HttpClient.GetAsync(...) "deadlock" when there is no internet connection - c#

Recenly I found some sort of bug while using System.Net.http.HttpClient().GetAsync(...)
When in the middle process of GetAsync(..), if I disconnect my internet connection, the app just act like deadlock, and no exception catch, even I wait for few minutes.
Here is the example of code:
private async Task<WriteableBitmap> loadImageAsync(string url)
{
using (var httpClient = new HttpClient())
{
//When this line of code start execute, immediately disable the internet connection
var response = await httpClient.GetAsync(url);
//Once it get stuck at GetAsync(..), it won't execute the codes below
byte[] imageByte = await response.Content.ReadAsByteArrayAsync();
WriteableBitmap bitmap = new WriteableBitmap(1920, 1080);
using (InMemoryRandomAccessStream randomStream = new InMemoryRandomAccessStream())
{
using (DataWriter writer = new DataWriter(randomStream))
{
writer.WriteBytes(imageByte);
await writer.StoreAsync();
await writer.FlushAsync();
writer.DetachStream();
}
randomStream.Seek(0);
await bitmap.SetSourceAsync(randomStream);
}
return bitmap;
}
}
I had try to set timeout to the httpClient, but it won't care what I set if there is no internet connection.
Is there any way to catch if the internet connection is disconnected, or did I miss something?
Edit: another async task that await loadImageAsync(..)
public async Task GetImagesAsync()
{
//skip if image is currently loading
if (IsImageLoading) { return; }
IsImageLoading = true;
ImageObjs.Clear();
try
{
int numberOfLoad = (int)((double)_LocalSettings.Values[NUMBER_OF_IMAGE_LOAD_SETTINGS]);
string region = _LocalSettings.Values[REGION].ToString();
#region Get Bing Image info
string jsonString = await new HttpClient().GetStringAsync($"http://www.bing.com/HPImageArchive.aspx?format=js&n={numberOfLoad}&mkt={region}");
ImageCollection imageCollection = JsonConvert.DeserializeObject<ImageCollection>(jsonString);
#endregion
#region Download images
foreach (var i in imageCollection.images)
{
string url;
if (Windows.Foundation.Metadata.ApiInformation.IsTypePresent("Windows.UI.ViewManagement.StatusBar"))
{
url = i.url.Replace("1920x1080", "1080x1920");
}
else
{
url = i.url;
}
ImageObjs.Add(new ImageObj(await loadImageAsync(bingLink + url), i.copyright, url, i.copyrightlink, region));
}
showToastNotification(_MainViewModel.ImageObjs.Count.ToString() + " images loaded.");
#endregion
}
catch
{
//if connection failed
}
finally
{
IsImageLoading = false;
}
}

have you tried setting timeout with CancelationToken?
var tokenSource = new CancellationTokenSource();
tokenSource.CancelAfter(TimeSpan.FromSeconds(15));
try
{
var response = await httpClient.GetAsync(url, tokenSource.Token);
}
catch (TaskCanceledException ex)
{
// handle timeout here
}

Related

ASP.NET Core with WebSockets - WebSocket handshake never occurs

I am very new to C# programming, having previously only worked with Java. This project I am building should be very straightforward - we have a web page with a selection of foreign currency pairs. The element chosen is sent to the server, which responds with a hardcoded value of their exchange rate. The requirement is that both actions are implemented through the use of WebSockets. Here is the JS code on my page:
var protocol;
var wsUri;
var socket;
window.onload = function(e) {
e.preventDefault();
protocol = location.protocol === "https:" ? "wss:" : "ws:";
wsUri = protocol + "//" + window.location.host;
socket = new WebSocket(wsUri);
socket.onopen = e => {
console.log("socket opened", e);
};
document.getElementById("currencypair").onchange = function()
{
var selector = document.getElementById("currencypair");
var text = selector.options[selector.selectedIndex].text;
socket.send(text);
};
socket.onmessage = function (evt) {
var receivedMessage = evt.data;
document.getElementById("output").html(receivedMessage);
};
};
Here is a snippet of the Startup.cs class Configure method:
app.UseWebSockets();
app.UseMiddleware<WebSocketMiddleware>();
And here is the middleware class to process requests.
public class WebSocketMiddleware
{
private readonly RequestDelegate _next;
public WebSocketMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
if (!context.WebSockets.IsWebSocketRequest)
{
await _next.Invoke(context);
return;
}
var ct = context.RequestAborted;
using (var socket = await context.WebSockets.AcceptWebSocketAsync())
{
while (true)
{
var stringReceived = await ReceiveStringAsync(socket, ct);
if (CurrencyPairCollection.CurrencyPairs.TryGetValue(stringReceived, out var value))
{
await SendStringAsync(socket, value.ToString(), ct);
}
else
{
throw new Exception("Unexpected value");
}
await Task.Delay(1000, ct);
}
}
}
private static async Task<string> ReceiveStringAsync(WebSocket socket, CancellationToken ct = default(CancellationToken))
{
var buffer = new ArraySegment<byte>();
using (var ms = new MemoryStream())
{
WebSocketReceiveResult result;
do
{
ct.ThrowIfCancellationRequested();
result = await socket.ReceiveAsync(buffer, ct);
ms.Write(buffer.Array, buffer.Offset, result.Count);
}
while (!result.EndOfMessage);
ms.Seek(0, SeekOrigin.Begin);
if (result.MessageType != WebSocketMessageType.Text || result.Count.Equals(0))
{
throw new Exception("Unexpected message");
}
using (var reader = new StreamReader(ms, Encoding.UTF8))
{
return await reader.ReadToEndAsync();
}
}
}
private static Task SendStringAsync(WebSocket socket, string data, CancellationToken ct = default(CancellationToken))
{
var segment = new ArraySegment<byte>(Encoding.UTF8.GetBytes(data));
return socket.SendAsync(segment, WebSocketMessageType.Text, true, ct);
}
}
Please mind I was working with the following example which contains mistakes listed by people in the comment section. I did my best to resolve them, however due to my limited experience, that may be where the fault lies.
https://www.softfluent.com/blog/dev/Using-Web-Sockets-with-ASP-NET-Core
Basically, upon running the app the browser console immediately reports this:
WebSocket connection to 'ws://localhost:51017/' failed: Error during WebSocket handshake: Unexpected response code: 200
I have been able to answer my own question. So in Startup.cs of which I provided only a snippet, a call to app.UseMvc() is made right before the lines I have already shared. This is generated by the default template. The trick was to move this call to below the following:
app.UseWebSockets();
app.UseMiddleware<WebSocketMiddleware>();
as otherwise the request pipeline is disrupted.
This will allow our socket to open, however without changing the following line in async Task ReceiveStringAsync(...)
var buffer = new ArraySegment<byte>();
to
var buffer = new ArraySegment<byte>(new byte[8192]);
it will still close prematurely. Next, just needed to correct JS syntax error. Changed
document.getElementById("output").html(receivedMessage);
to
document.getElementById("output").value = receivedMessage;
That's it, it works.

Parallel HTTP requests using System.Net.Http.HttpClient

I'm trying to figure out the correct way to parallelize HTTP requests using Task and async/await. I'm using the HttpClient class which already has async methods for retrieving data. If I just call it in a foreach loop and await the response, only one request gets sent at a time (which makes sense because during the await, control is returning to our event loop, not to the next iteration of the foreach loop).
My wrapper around HttpClient looks as such
public sealed class RestClient
{
private readonly HttpClient client;
public RestClient(string baseUrl)
{
var baseUri = new Uri(baseUrl);
client = new HttpClient
{
BaseAddress = baseUri
};
}
public async Task<Stream> GetResponseStreamAsync(string uri)
{
var resp = await GetResponseAsync(uri);
return await resp.Content.ReadAsStreamAsync();
}
public async Task<HttpResponseMessage> GetResponseAsync(string uri)
{
var resp = await client.GetAsync(uri);
if (!resp.IsSuccessStatusCode)
{
// ...
}
return resp;
}
public async Task<T> GetResponseObjectAsync<T>(string uri)
{
using (var responseStream = await GetResponseStreamAsync(uri))
using (var sr = new StreamReader(responseStream))
using (var jr = new JsonTextReader(sr))
{
var serializer = new JsonSerializer {NullValueHandling = NullValueHandling.Ignore};
return serializer.Deserialize<T>(jr);
}
}
public async Task<string> GetResponseString(string uri)
{
using (var resp = await GetResponseStreamAsync(uri))
using (var sr = new StreamReader(resp))
{
return sr.ReadToEnd();
}
}
}
And the code invoked by our event loop is
public async void DoWork(Action<bool> onComplete)
{
try
{
var restClient = new RestClient("https://example.com");
var ids = await restClient.GetResponseObjectAsync<IdListResponse>("/ids").Ids;
Log.Info("Downloading {0:D} items", ids.Count);
using (var fs = new FileStream(#"C:\test.json", FileMode.Create, FileAccess.Write, FileShare.Read))
using (var sw = new StreamWriter(fs))
{
sw.Write("[");
var first = true;
var numCompleted = 0;
foreach (var id in ids)
{
Log.Info("Downloading item {0:D}, completed {1:D}", id, numCompleted);
numCompleted += 1;
try
{
var str = await restClient.GetResponseString($"/info/{id}");
if (!first)
{
sw.Write(",");
}
sw.Write(str);
first = false;
}
catch (HttpException e)
{
if (e.StatusCode == HttpStatusCode.Forbidden)
{
Log.Warn(e.ResponseMessage);
}
else
{
throw;
}
}
}
sw.Write("]");
}
onComplete(true);
}
catch (Exception e)
{
Log.Error(e);
onComplete(false);
}
}
I've tried a handful of different approaches involving Parallel.ForEach, Linq.AsParallel, and wrapping the entire contents of the loop in a Task.
The basic idea is to keep of track of all the asynchronous tasks, and awaiting them at once. The simplest way to do this is to extract the body of your foreach to a separate asynchronous method, and do something like this:
var tasks = ids.Select(i => DoWorkAsync(i));
await Task.WhenAll(tasks);
This way, the individual tasks are issued separately (still in sequence, but without waiting for the I/O to complete), and you await them all at the same time.
Do note that you will also need to do some configuration - HTTP is throttled by default to only allow two simultaneous connections to the same server.

System.Net.Http.HttpClient in [Universal Windows Platform] not working properly

I wrote simple method for getting data from (online) REST Service:
public async Task<Object> GetTask()
{
try
{
using (HttpClient client = new HttpClient())
{
client.BaseAddress = new Uri("http://111.111.111.111:8080/");
HttpResponseMessage result = await client.GetAsync("ABC/CDE/getsomeinfo");
if (result.IsSuccessStatusCode)
{
//Deserialize
}
}
}
catch (Exception ex)
{
Debug.WriteLine("Error" + ex);
}
return null;
}
Whenever i run this on UWP i'm getting catch exception:
The text associated with this error code could not be found.
A connection with the server could not be established
HResult 2147012867
Im trying to connect my client with restapi in internal network. In forms same code is working properly.
Try this
HttpResponseMessage response;
public async Task<string> webserviceResponse(string HttpMethod)
{
// check internet connection is available or not
if (NetworkInterface.GetIsNetworkAvailable() == true)
{
// CancellationTokenSource cts = new CancellationTokenSource(2000); // 2 seconds
HttpClient client = new HttpClient();
MultipartFormDataContent mfdc = new MultipartFormDataContent();
mfdc.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data");
string GenrateUrl = "your url";
if (HttpMethod == "POST")
{
response = await client.PostAsync(GenrateUrl, mfdc);
}
else if (HttpMethod == "PUT")
{
response = await client.PutAsync(GenrateUrl, mfdc);
}
else if (HttpMethod == "GET")
{
response = await client.GetAsync(GenrateUrl);
}
var respon = await response.Content.ReadAsStringAsync();
string convert_response = respon.ToString();
return convert_response;
}
else
{
return "0";
}
}

async task cancellation c# xamarin

I have a functionality of search users. I have provided a textview and on that textview changed method I'm firing a method to get data from web server. But I'm facing problem when user types letter, because all the api hits done in async task. Service should be hit after 100 milli-sec of wait, means if user types a letter "a" then doesn't type for 100 milli-sec then We have to hit the service. But if user types "a" then "b" then "c", so one service should be hit for "abc", not for all.
I followed the official link, but it doesn't help me
https://msdn.microsoft.com/en-us/library/jj155759.aspx
So basically here is my code
textview.TextChange+= (sender,e) =>{
CancellationTokenSource cts = new CancellationTokenSource();
await Task.Delay(500);
// here some where I have to pass cancel token
var lst = await APIClient.Instance.GetUserSearch("/user/get?searchTerm=" + newText, "application/json",cts);
if (lst != null && lst.Count > 0){
lstSearch.AddRange(lst);
}
}
Here is my method to GetUser
public async Task<JResponse> GetUserSearch<JResponse>(string uri, string contentType,CancellationToken cts)
{
try
{
Console.Error.WriteLine("{0}", RestServiceBaseAddress + uri);
string url = string.Format("{0}{1}", RestServiceBaseAddress, uri);
var request = (HttpWebRequest)WebRequest.Create(url);
request.ContentType = contentType;
if (Utility.CurrentUser != null && !string.IsNullOrWhiteSpace(Utility.CurrentUser.AuthToken))
{
request.Headers.Add("api_key", Utility.CurrentUser.AuthToken);
}
request.Method = "POST";
var payload = body.ToString();
request.ContentLength = payload.Length;
byte[] byteArray = Encoding.UTF8.GetBytes(body.ToString());
request.ContentLength = byteArray.Length;
using (var stream = await request.GetRequestStreamAsync())
{
stream.Write(byteArray, 0, byteArray.Length);
stream.Close();
}
using (var webResponse = await request.GetResponseAsync())
{
var response = (HttpWebResponse)webResponse;
using (var reader1 = new StreamReader(response.GetResponseStream()))
{
Console.WriteLine("Finished : {0}", uri);
var responseStr = reader1.ReadToEnd();
var responseObj = JsonConvert.DeserializeObject<JResponse>(
responseStr,
new JsonSerializerSettings()
{
MissingMemberHandling = MissingMemberHandling.Ignore,
NullValueHandling = NullValueHandling.Ignore
});
return responseObj;
}
}
}
catch (System.Exception ex)
{
Utility.ExceptionHandler("APIClient", "ProcessRequestAsync", ex);
}
return default(JResponse);
}
In your example, you are creating a CancellationTokenSource - you need to hold a reference to it, so that the next time the handler is invoked, the previous search can be cancelled. Here is an example console app that you should be able to run, but the important piece is in the handler.
private CancellationTokenSource _cts;
private async void TextChangedHandler(string text) // async void only for event handlers
{
try
{
_cts?.Cancel(); // cancel previous search
}
catch (ObjectDisposedException) // in case previous search completed
{
}
using (_cts = new CancellationTokenSource())
{
try
{
await Task.Delay(TimeSpan.FromSeconds(1), _cts.Token); // buffer
var users = await _userService.SearchUsersAsync(text, _cts.Token);
Console.WriteLine($"Got users with IDs: {string.Join(", ", users)}");
}
catch (TaskCanceledException) // if the operation is cancelled, do nothing
{
}
}
}
Be sure to pass the CancellationToken into all of the async methods, including those that perform the web request, this way you signal the cancellation right down to the lowest level.
Try to use timer. First time then you change text - you create it. Then you change text after that - you restart timer. If you don't change text for 700 milliseconds - timer will fire PerformeSearch method. Use Timeout.Infinite for timer period parameter to prevent it from restarting.
textview.TextChange += (sender,e) =>
{
if (_fieldChangeTimer == null)
_fieldChangeTimer = new Timer(delegate
{
PerformeSearch();
}, null, 700, Timeout.Infinite);
else
{
_fieldChangeTimer.Change(700, Timeout.Infinite);
}
};
Instantiate the CancellationTokenSource.
cts = new CancellationTokenSource(); Example method
private void cancelButton_Click(object sender, RoutedEventArgs e)
{
if (cts != null)
{
cts.Cancel();
}
}

Error checking when using HttpClient for asynchronous file downloads

This question is a followup to Threading issues when using HttpClient for asynchronous file downloads.
To get a file transfer to complete asynchronously using HttpClient, you need to add HttpCompletionOption.ResponseHeadersRead to the SendAsync request. Thus, when that call completes, you will be able to determine that all was well with the request and the response headers by adding a call to EnsureSuccessStatusCode. However the data is possibly still being transferred at this point.
How can you detect errors which happen after the headers are returned but before the data transfer is complete? How would said errors manifest themselves?
Some example code follows, with the point of the question marked at line 109)with the comment: "// *****WANT TO DO MORE ERROR CHECKING HERE**"
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
namespace TestHttpClient2
{
class Program
{
/* Use Yahoo portal to access quotes for stocks - perform asynchronous operations. */
static string baseUrl = "http://real-chart.finance.yahoo.com/";
static string requestUrlFormat = "/table.csv?s={0}&d=0&e=1&f=2016&g=d&a=0&b=1&c=1901&ignore=.csv";
static void Main(string[] args)
{
var activeTaskList = new List<Task>();
string outputDirectory = "StockQuotes";
if (!Directory.Exists(outputDirectory))
{
Directory.CreateDirectory(outputDirectory);
}
while (true)
{
Console.WriteLine("Enter symbol or [ENTER] to exit:");
string symbol = Console.ReadLine();
if (string.IsNullOrEmpty(symbol))
{
break;
}
Task downloadTask = DownloadDataForStockAsync(outputDirectory, symbol);
if (TaskIsActive(downloadTask))
{
// This is an asynchronous world - lock the list before updating it!
lock (activeTaskList)
{
activeTaskList.Add(downloadTask);
}
}
else
{
Console.WriteLine("task completed already?!??!?");
}
CleanupTasks(activeTaskList);
}
Console.WriteLine("Cleaning up");
while (CleanupTasks(activeTaskList))
{
Task.Delay(1).Wait();
}
}
private static bool CleanupTasks(List<Task> activeTaskList)
{
// reverse loop to allow list item deletions
// This is an asynchronous world - lock the list before updating it!
lock (activeTaskList)
{
for (int i = activeTaskList.Count - 1; i >= 0; i--)
{
if (!TaskIsActive(activeTaskList[i]))
{
activeTaskList.RemoveAt(i);
}
}
return activeTaskList.Count > 0;
}
}
private static bool TaskIsActive(Task task)
{
return task != null
&& task.Status != TaskStatus.Canceled
&& task.Status != TaskStatus.Faulted
&& task.Status != TaskStatus.RanToCompletion;
}
static async Task DownloadDataForStockAsync(string outputDirectory, string symbol)
{
try
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(baseUrl);
client.Timeout = TimeSpan.FromMinutes(5);
string requestUrl = string.Format(requestUrlFormat, symbol);
var request = new HttpRequestMessage(HttpMethod.Post, requestUrl);
var response = await client.SendAsync(request,
HttpCompletionOption.ResponseHeadersRead);
response.EnsureSuccessStatusCode();
using (var httpStream = await response.Content.ReadAsStreamAsync())
{
var timestampedName = FormatTimestampedString(symbol, true);
var filePath = Path.Combine(outputDirectory, timestampedName + ".csv");
using (var fileStream = File.Create(filePath))
{
await httpStream.CopyToAsync(fileStream);
}
}
// *****WANT TO DO MORE ERROR CHECKING HERE*****
}
}
catch (HttpRequestException ex)
{
Console.WriteLine("Exception on thread: {0}: {1}\r\n",
System.Threading.Thread.CurrentThread.ManagedThreadId,
ex.Message,
ex.StackTrace);
}
catch (Exception ex)
{
Console.WriteLine("Exception on thread: {0}: {1}\r\n",
System.Threading.Thread.CurrentThread.ManagedThreadId,
ex.Message,
ex.StackTrace);
}
}
static volatile string lastTimestampedString = string.Empty;
static volatile string dummy = string.Empty;
static string FormatTimestampedString(string message, bool uniquify = false)
{
// This is an asynchronous world - lock the shared resource before using it!
lock (dummy)
//lock (lastTimestampedString)
{
Console.WriteLine("IN - Thread: {0:D2} lastTimestampedString: {1}",
System.Threading.Thread.CurrentThread.ManagedThreadId,
lastTimestampedString);
string newTimestampedString;
while (true)
{
DateTime lastDateTime = DateTime.Now;
newTimestampedString = string.Format(
"{1:D4}_{2:D2}_{3:D2}_{4:D2}_{5:D2}_{6:D2}_{7:D3}_{0}",
message,
lastDateTime.Year, lastDateTime.Month, lastDateTime.Day,
lastDateTime.Hour, lastDateTime.Minute, lastDateTime.Second,
lastDateTime.Millisecond
);
if (!uniquify)
{
break;
}
if (newTimestampedString != lastTimestampedString)
{
break;
}
//Task.Delay(1).Wait();
};
lastTimestampedString = newTimestampedString;
Console.WriteLine("OUT - Thread: {0:D2} lastTimestampedString: {1}",
System.Threading.Thread.CurrentThread.ManagedThreadId,
lastTimestampedString);
return lastTimestampedString;
}
}
}
}
I have copied and slightly cleaned up the relevant code.
var request = new HttpRequestMessage(HttpMethod.Post, requestUrl);
var response = await client.SendAsync(request,
HttpCompletionOption.ResponseHeadersRead);
response.EnsureSuccessStatusCode();
using (var httpStream = await response.Content.ReadAsStreamAsync())
{
var timestampedName = FormatTimestampedString(symbol, true);
var filePath = Path.Combine(outputDirectory, timestampedName + ".csv");
using (var fileStream = File.Create(filePath))
{
await httpStream.CopyToAsync(fileStream);
}
}
The question is, what if something goes wrong during reading the stream and copying it into your file?
All logical errors have already been addressed as part of the HTTP request and response cycle: the server has received your request, it has decided it is valid, it has responded with success (header portion of response), and it is now sending you the result (body portion of response).
The only errors that could occur now are things like the server crashing, the connection being lost, etc. My understanding is that these will manifest as HttpRequestException, meaning you can write code like this:
try
{
using (var httpStream = await response.Content.ReadAsStreamAsync())
{
var timestampedName = FormatTimestampedString(symbol, true);
var filePath = Path.Combine(outputDirectory, timestampedName + ".csv");
using (var fileStream = File.Create(filePath))
{
await httpStream.CopyToAsync(fileStream);
}
}
}
catch (HttpRequestException e)
{
...
}
The documenation doesn't say much, unfortunately. The reference source doesn't either. So your best bet is to start with this and maybe log all exceptions that are not HttpRequestException in case there is another exception type that could be thrown during the download of the response body.
If you want to narrow it down to the part which is between the header read and the content read, you actually leave yourself with the asynchronous buffer read:
var httpStream = await response.Content.ReadAsStreamAsync();
If you look whats going on inside the method, you'll see:
public Task<Stream> ReadAsStreamAsync()
{
this.CheckDisposed();
TaskCompletionSource<Stream> tcs = new TaskCompletionSource<Stream>();
if (this.contentReadStream == null && this.IsBuffered)
{
this.contentReadStream = new MemoryStream(this.bufferedContent.GetBuffer(),
0, (int)this.bufferedContent.Length,
false, false);
}
if (this.contentReadStream != null)
{
tcs.TrySetResult(this.contentReadStream);
return tcs.Task;
}
this.CreateContentReadStreamAsync().ContinueWithStandard(delegate(Task<Stream> task)
{
if (!HttpUtilities.HandleFaultsAndCancelation<Stream>(task, tcs))
{
this.contentReadStream = task.Result;
tcs.TrySetResult(this.contentReadStream);
}
});
return tcs.Task;
}
CreateContentReadStreamAsync is the one doing all the reading, internally, it will call LoadIntoBufferAsync, which you can find here.
Basically, you can see the it encapsulates IOException and ObjectDisposedException, or an ArgumentOutOfRangeException is the buffer is larger than 2GB (although i think that will be highly rare).

Categories

Resources