Why no error notification for UploadFileAsync with WebClient? - c#

When I execute the following code:
public static async Task UploadFile(string serverPath, string pathToFile, string authToken)
{
serverPath = #"C:\_Series\S1\The 100 S01E03.mp4";
var client = new WebClient();
var uri = new Uri($"http://localhost:50424/api/File/Upload?serverPath={WebUtility.UrlEncode(serverPath)}");
client.UploadProgressChanged += UploadProgressChanged;
client.UploadFileCompleted += UploadCompletedCallback;
//client.UploadFileAsync(uri, "POST", pathToFile);
client.UploadFile(uri, "POST", pathToFile);
}
I get the exception:
System.Net.WebException: 'The remote server returned an error: (404)
Not Found.'
I'm not too worried about the 404, I'm busy tracing down why the WebClient can't find it, but my big concern is that if I call UploadFileAsync with the same uri, the method just executes as if nothing is wrong.
The only indication that something is wrong is that neither of the two event handlers is invoked. I strongly suspect that I don't get an exception because the async call is not async/await but event based, but then I would expect some kind of event or property that indicates an exception has occurred.
How is one supposed to use code that hides errors like this, especially network errors which are relatively more common, in production?

Why no error notification for UploadFileAsync with WebClient?
Citing WebClient.UploadFileAsync Method (Uri, String, String) Remarks
The file is sent asynchronously using thread resources that are automatically allocated from the thread pool. To receive notification when the file upload completes, add an event handler to the UploadFileCompleted event.
Emphasis mine.
You get no errors because it is being executed on another thread so as not to block the current thread. To see the error you can access it in the stated event handler via the UploadFileCompletedEventArgs.Exception.
I was curious as to why using WebClient and not HttpClient which is already primarily async, but then my assumption was because of the upload progress.
I would suggest wrapping the WebClient call with event handlers in a Task using a TaskCompletionSource to take advantage of TAP.
The following is similar to the examples provided here How to: Wrap EAP Patterns in a Task
public static async Task UploadFileAsync(string serverPath, string pathToFile, string authToken, IProgress<int> progress = null) {
serverPath = #"C:\_Series\S1\The 100 S01E03.mp4";
using (var client = new WebClient()) {
// Wrap Event-Based Asynchronous Pattern (EAP) operations
// as one task by using a TaskCompletionSource<TResult>.
var task = client.createUploadFileTask(progress);
var uri = new Uri($"http://localhost:50424/api/File/Upload?serverPath={WebUtility.UrlEncode(serverPath)}");
client.UploadFileAsync(uri, "POST", pathToFile);
//wait here while the file uploads
await task;
}
}
Where createUploadFileTask is a custom extension method used to wrap the Event-Based Asynchronous Pattern (EAP) operations of the WebClient as one task by using a TaskCompletionSource<TResult>.
private static Task createTask(this WebClient client, IProgress<int> progress = null) {
var tcs = new TaskCompletionSource<object>();
#region callbacks
// Specifiy the callback for UploadProgressChanged event
// so it can be tracked and relayed through `IProgress<T>`
// if one is provided
UploadProgressChangedEventHandler uploadProgressChanged = null;
if (progress != null) {
uploadProgressChanged = (sender, args) => progress.Report(args.ProgressPercentage);
client.UploadProgressChanged += uploadProgressChanged;
}
// Specify the callback for the UploadFileCompleted
// event that will be raised by this WebClient instance.
UploadFileCompletedEventHandler uploadCompletedCallback = null;
uploadCompletedCallback = (sender, args) => {
// unsubscribing from events after asynchronous
// events have completed
client.UploadFileCompleted -= uploadCompletedCallback;
if (progress != null)
client.UploadProgressChanged -= uploadProgressChanged;
if (args.Cancelled) {
tcs.TrySetCanceled();
return;
} else if (args.Error != null) {
// Pass through to the underlying Task
// any exceptions thrown by the WebClient
// during the asynchronous operation.
tcs.TrySetException(args.Error);
return;
} else
//since no result object is actually expected
//just set it to null to allow task to complete
tcs.TrySetResult(null);
};
client.UploadFileCompleted += uploadCompletedCallback;
#endregion
// Return the underlying Task. The client code
// waits on the task to complete, and handles exceptions
// in the try-catch block there.
return tcs.Task;
}
Going one step further and creating another extension method to wrap the upload file to make it await able...
public static Task PostFileAsync(this WebClient client, Uri address, string fileName, IProgress<int> progress = null) {
var task = client.createUploadFileTask(progress);
client.UploadFileAsync(address, "POST", fileName);//this method does not block the calling thread.
return task;
}
Allowed your UploadFile to be refactored to
public static async Task UploadFileAsync(string serverPath, string pathToFile, string authToken, IProgress<int> progress = null) {
using (var client = new WebClient()) {
var uri = new Uri($"http://localhost:50424/api/File/Upload?serverPath={WebUtility.UrlEncode(serverPath)}");
await client.PostFileAsync(uri, pathToFile, progress);
}
}
This now allow you to call the upload asynchronously and even keep track of the progress with your very own Progress Reporting (Optional)
For example if in an XAML based platform
public class UploadProgressViewModel : INotifyPropertyChanged, IProgress<int> {
public int Percentage {
get {
//...return value
}
set {
//...set value and notify change
}
}
public void Report(int value) {
Percentage = value;
}
}
Or using the out of the box Progress<T> Class
So now you should be able to upload the file without blocking the thread and still be able to await it, get progress notifications, and handle exceptions, provided you have a try/catch in place.

Related

Call a WebService Aysnchronously inside IHttpAsyncHandler

I am trying to call a WebService inside my IHttpAsyncHandler and I see there is an answer like this
Using an IHttpAsyncHandler to call a WebService Asynchronously
I have questions about the answer. I appreciate if someone can help me.
It has
Task webClientDownloadTask = webClientDownloadCompletionSource.Task;
My questions are
webClientDownloadCompletionSource is not related to Webclient (client) object, so what is the point of doing this:
// Get the TCS's task so that we can append some continuations
Task webClientDownloadTask = webClientDownloadCompletionSource.Task;
What is "taskCompletionSource" in here:
// Signal the TCS that were done (we don't actually look at the bool result, but it's needed)
taskCompletionSource.SetResult(true);
Why I dispose() the WebClient in the ContinueWith() callback, why not just dispose() WEbClient after we set the taskCompletionSource.SetResult(true);?
// Always dispose of the client once the work is completed
webClientDownloadTask.ContinueWith(
_ =>
{
client.Dispose();
},
TaskContinuationOptions.ExecuteSynchronously);
Here is the full code:
public class MyAsyncHandler : IHttpAsyncHandler
{
public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
{
// NOTE: the result of this operation is void, but TCS requires some data type so we just use bool
TaskCompletionSource<bool> webClientDownloadCompletionSource = new TaskCompletionSource<bool>();
WebClient webClient = new WebClient())
HttpContext currentHttpContext = HttpContext.Current;
// Setup the download completed event handler
client.DownloadDataCompleted += (o, e) =>
{
if(e.Cancelled)
{
// If it was canceled, signal the TCS is cacnceled
// NOTE: probably don't need this since you have nothing canceling the operation anyway
webClientDownloadCompletionSource.SetCanceled();
}
else if(e.Error != null)
{
// If there was an exception, signal the TCS with the exception
webClientDownloadCompletionSource.SetException(e.Error);
}
else
{
// Success, write the response
currentHttpContext.Response.ContentType = "text/xml";
currentHttpContext.Response.OutputStream.Write(e.Result, 0, e.Result.Length);
// Signal the TCS that were done (we don't actually look at the bool result, but it's needed)
taskCompletionSource.SetResult(true);
}
};
string url = "url_web_service_url";
// Kick off the download immediately
client.DownloadDataAsync(new Uri(url));
// Get the TCS's task so that we can append some continuations
Task webClientDownloadTask = webClientDownloadCompletionSource.Task;
// Always dispose of the client once the work is completed
webClientDownloadTask.ContinueWith(
_ =>
{
client.Dispose();
},
TaskContinuationOptions.ExecuteSynchronously);
// If there was a callback passed in, we need to invoke it after the download work has completed
if(cb != null)
{
webClientDownloadTask.ContinueWith(
webClientDownloadAntecedent =>
{
cb(webClientDownloadAntecedent);
},
TaskContinuationOptions.ExecuteSynchronously);
}
// Return the TCS's Task as the IAsyncResult
return webClientDownloadTask;
}
public void EndProcessRequest(IAsyncResult result)
{
// Unwrap the task and wait on it which will propagate any exceptions that might have occurred
((Task)result).Wait();
}
public bool IsReusable
{
get
{
return true; // why not return true here? you have no state, it's easily reusable!
}
}
public void ProcessRequest(HttpContext context)
{
}
}
Check this for an idea of what it's used for. There is also an example at the end
TaskCompletionSource
In many scenarios, it is useful to enable a Task to represent
an external asynchronous operation. TaskCompletionSource{TResult} is
provided for this purpose. It enables the creation of a task that can
be handed out to consumers, and those consumers can use the members of
the task as they would any other. However, unlike most tasks, the
state of a task created by a TaskCompletionSource is controlled
explicitly by the methods on TaskCompletionSource. This enables the
completion of the external asynchronous operation to be propagated to
the underlying Task. The separation also ensures that consumers are
not able to transition the state without access to the corresponding
TaskCompletionSource.

How to cancel previous Task if new request recieved?

I will try to simplify my situation here to be more clean and concise. So, I am working on a WinRT application where user enters text in a TextBox and in its TextChanged event after 2 seconds have elapsed I need to make a remote request to fetch data based on user text.
Now user enters text and a web request has been initialized but immediately user writes another term. So, I need to cancel the first web request and fire the new one.
Consider the following as my code :
private CancellationTokenSource cts;
public HomePageViewModel()
{
cts = new CancellationTokenSource();
}
private async void SearchPeopleTextChangedHandler(SearchPeopleTextChangedEventArgs e)
{
//Cancel previous request before making new one
//GetMembers() is using simple HttpClient to PostAsync() and get response
var members = await _myService.GetMembers(someId, cts.Token);
//other stuff based on members
}
I know CancellationToken plays a role here but I just cannot figure out how.
You've already almost got it. The core idea is that a single CancellationTokenSource can only be canceled once, so a new one has to be created for each operation.
private CancellationTokenSource cts;
private async void SearchPeopleTextChangedHandler(SearchPeopleTextChangedEventArgs e)
{
// If there's a previous request, cancel it.
if (cts != null)
cts.Cancel();
// Create a CTS for this request.
cts = new CancellationTokenSource();
try
{
var members = await _myService.GetMembers(someId, cts.Token);
//other stuff based on members
}
catch (OperationCanceledException)
{
// This happens if this operation was cancelled.
}
}
I would implement the GetMembers method like this:
private async Task<List<Member>> GetMembers(int id, CancellationToken token)
{
try
{
token.ThrowIfCancellationRequested();
HttpResponseMessage response = null;
using (HttpClient client = new HttpClient())
{
response = await client.PostAsync(new Uri("http://apiendpoint"), content)
.AsTask(token);
}
token.ThrowIfCancellationRequested();
// Parse response and return result
}
catch (OperationCanceledException ocex)
{
return null;
}
}
The rest is just calling the cts.Cancel() method and creating a new instance of CancellationTokenSource before calling GetMembers each time in the handler. Of course, as #Russell Hickey mentioned, cts should be global. (and even static if there are multiple instances of this class and you always want to cancel the GetMembers method when this handler is invoked. Usually I also have a class which wraps the result and has an additional property IsSuccessful to distinguish a real null result from a failed operation.

How can I cancel and progress report a task that returned by WebClient.DownloadDataTaskAsync method?

I'm learning task-parallel-library. I have some old code that use WebClient class to download data from web. I want to convert my previous code that using Event-based Asynchronous Pattern(EAP) to Task-based Asynchronous Pattern (TAP)
My old code were below:
WebClient client1 = new WebClient();
client1.DownloadDataCompleted += (o, e)=>
{
if (e.Cancelled)
{
//code that update UI report download has been canceled.
}
else
{
byte[] s = e.Result;
//code that update UI report downloads has been completed.
}
};
client1.DownloadProgressChanged += ( o, e) =>
{
//code that update UI report downloading progress.
updateProgress(e.ProgressPercentage);
};
//start download asynchronous
client1.DownloadDataAsync(new Uri("http://stackoverflow.com/"));
//code to cancel download.
client1.CancelAsync();
Now using Task API, I had code:
WebClient client2 = new WebClient();
Task<byte[]> task = client2.DownloadDataTaskAsync("http://stackoverflow.com/");
task.ContinueWith((antecedent) =>
{
byte[] s = antecedent.Result;
//code that updateUI report download has been completed.
});
//TODO how to code that can cancel the download and report progress?
So My questions are:
When using the task method DownloadDataTaskAsync, does the WebClient class has build-in api that I can cancel the download and report the download progress?
You can extend WebClient:
public static class WebClientExtensions
{
public static async Task<byte[]> DownloadDataTaskAsync(this WebClient webClient, string address, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
using (cancellationToken.Register(webClient.CancelAsync))
{
return await webClient.DownloadDataTaskAsync(address);
}
}
}
Then call the extension method:
var data = await webClient.DownloadDataTaskAsync("http://stackoverflow.com/", cancellationToken);
There are no overloads that support cancellation so you can't cancel that async action (You may want to read How do I cancel non-cancelable async operations?).
You can however add a timeout or cancellation on top of that action that will let your code flow proceed.
For progress reporting you might want to look at WebClient.DownloadProgressChanged Event. It seems to fit your needs.
public static async Task<byte[]> DownloadDataTaskAsync(this WebClient obj, Uri address, CancellationToken cancellationToken)
{
var tcs = new TaskCompletionSource<bool>();
var task = obj.DownloadDataTaskAsync(address);
using (cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).TrySetResult(true), tcs))
{
if (task != await Task.WhenAny(task, tcs.Task))
{
throw new OperationCanceledException(cancellationToken);
}
}
return await task;
}
Modified version from #Dartal
Use task completion source to solve the problem, or you can reference How do I cancel non-cancelable async operations?
Then, you can use CancelAsync to force close the connection
Aborting a WebClient.DownloadFileAsync operation

Is there a way to pass in callbacks when calling Async methods in WP8/Silverlight

I've been writing Windows Phone 8 code that calls a SOAP web service backend. From what I've read, the typical pattern is this:
var client = new KitchenPCSoapClient();
client.LogonCompleted += client_LogonCompleted;
client.LogonAsync(username, password);
To me, this just seems counter intuitive. If I call LogonAsync multiple times with the same client, I don't necessarily want it to use the same LogonCompleted callback every time.
What I'd like is a pattern more like JavaScript, where you pass in a reference to the callback function. More like:
var client = new KitchenPCSoapClient();
client.LogonAsync(username, password, client_LogonCompleted);
Is there a way to implement such a pattern, or should I just force myself to get used to setting the LogonCompleted property before I call LogonAsync, or set the userState property if I want to differentiate between different contexts?
You can make use of Dispatcher and call the function on UI side for this ...
I am doing like this
callback function
private void AddPricesHandler(CompletedEventArgs response, Exception e)
{
//your code gose here
}
Call proxy calss function
Proxy.AddData(AddPricesHandler, request);
proxy class calling webservice
public void AddData(Action<CompletedEventArgs, Exception> callback, IPVWorkflowService.CreateEditDeletePriceSourceRequest request)
{
_workflowProxy.CreateEditDeletePriceSourceAsync(request, callback);
_workflowProxy.CreateEditDeletePriceSourceCompleted+=new EventHandler<CreateEditDeletePriceSourceCompletedEventArgs>(_workflowProxy_CreateEditDeletePriceSourceCompleted);
}
completer function use dispatcher to call callback function
void _workflowProxy_CreateEditDeletePriceSourceCompleted(object sender, CreateEditDeletePriceSourceCompletedEventArgs e)
{
try
{
this.dispatcher.BeginInvoke((Action)(() =>
{
(e.UserState as Action<CompletedEventArgs, Exception>)(e, null);
}));
}
catch (Exception ex)
{
this.dispatcher.BeginInvoke((Action)(() =>
{
(e.UserState as Action<CompletedEventArgs, Exception>)(e, ex);
}));
}
finally
{
_workflowProxy.CreateEditDeletePriceSourceCompleted -= _workflowProxy_CreateEditDeletePriceSourceCompleted;
_workflowProxy = null;
}
}
The beauty of Async/Await is that you don't have to write callbacks, you just write code that looks like synchronous, but in the background it's executed asynchronously:
var client = new KitchenPCSoapClient();
await client.LogonAsync(username, password);
// you get here after the async logon is completed
// do stuff after logon
But if you really want to use callbacks, you can just use the method ContinueWith on the Task object, that is returned from asynchronous method:
var client = new KitchenPCSoapClient();
Task task = client.LogonAsync(username, password);
// this method is called when async execution of task is finished
task.ContinueWith(client_LogonCompleted);

Waiting for all async WebClient calls to finish

I am using the WebClient class in C#, 4.0. I need to hit a REST service with 30,000 different IDs, and grab the status result (200 or 404). Here is the method that makes the calls (eventCounter is a CountdownEvent object):
private void doWork()
{
initDB();
List<string> _lines = new List<string>();
//pull all UpcIds into a List
using (StreamReader _rdr = new StreamReader(#"C:\Users\kkohut\Dropbox\ROVI\Application Support\BestBuy\upc_test2.txt"))
{
string _line;
while ((_line = _rdr.ReadLine()) != null)
{
_lines.Add(_line);
}
}
numIds = _lines.Count();
for (int i = 0; i < numIds; i++)
{
string _upcId = _lines[i];
WebClient c = new WebClient();
c.DownloadDataCompleted += new DownloadDataCompletedEventHandler(c_DownloadDataCompleted);
c.DownloadDataAsync(new Uri(BASE_URL + _upcId), _upcId);
}
//this is not working correctly. Code execution hits this line and waits, without processing any of the
//the DownloadDataCompleted eventhandlers
eventCounter.Wait();
}
Here is the DownloadDataCompleted event handler
void c_DownloadDataCompleted(object sender, DownloadDataCompletedEventArgs e)
{
DataSet _ds = new DataSet();
string _upcId = e.UserState.ToString();
string _status = "404";
try
{
if (!e.Cancelled && e.Error == null)
{
string _result = System.Text.Encoding.UTF8.GetString(e.Result);
if (_result.IndexOf("<code>200</code>") > 0)
{
_status = "200";
}
}
}
catch (Exception ex)
{
_status = "404";
}
finally
{
updateDB(_upcId, _status);
eventCounter.Signal(1);
txtLog.Text += string.Format("{0}\t{1}\t{2}\r\n",ctr, _upcId, _status);
}
}
If I comment out the eventCounter.Wait() statement, the calls work, but I have no way of knowing when they complete. This is a winforms app, so as long as I keep the form running, all the calls complete. But if I uncomment the eventCounter.Wait() statement, no calls get processed. It appears that the Wait() statement is blocking the async calls to start with. Every example I have found uses this approach, but none of them are signaling the CountdownEvent in the completed event handler. Thoughts?
The WebClient Class implements the Event-based Asynchronous Pattern (EAP).
In this pattern, the XXXAsync Method captures the current SynchronizationContext (i.e. the UI thread in a WPF or WinForms application). When the operation completes, the event handler is executed in this context.
(See also: On which thread(s) does WebClient raise its events?)
Problem: If you invoke a blocking method on the UI thread, the event handler will not run before the blocking method returns.
Solution: Wait asynchronously for the CountdownEvent to complete, not synchronously.
You can use the ThreadPool.RegisterWaitForSingleObject Method to register a callback for the WaitHandle of the CountdownEvent.

Categories