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.
Related
I have a ASP.NET controller that internally calls an API and cache for answers and returns the answer from the first task completed. In general, API is much slower than cache and cache returns in 50ms while API returns in 2s at the 95th percentile.
Requirements:
Return answer from cache immediately if available. Otherwise wait for API and return available/empty response as the case maybe.
Once API call completes, we update the cache with the new answer if available.
Problem:
How to await API call and write to cache in a background thread without blocking controller?
Current flow:
Task<string>[] getDataTasks = new Task<string>[]
{
getDataFromAPI(),
getDataFromCache()
};
var finishedTaskIndex = Task.WaitAny(getDataTasks);
var response = getDataTasks[finishedTaskIndex].Result;
if (finishedTaskIndex == 1 && string.IsNullOrEmpty(response))
{
response = await getDataTasks[0];
writeToCacheTask(response); //fire and forget, this is non-blocking
}
**//Problem :- How to await for response from API and write to cache in a non-blocking manner** THIS DOES NOT WORK
Task.Run(async () =>
{
var apiResponse = await getDataTasks[0];
writeToCacheTask(apiResponse);
});
return response;
In the above, even though I am using a new thread to await the api call independent of the main thread, it does not work.
Does not work
Tried using ContinueWith on the Task with a callback with TaskContinuationOptions = ExecuteSynchronously as this continues on the same thread the task was invoked on. But I am perhaps understanding it wrongly.
Task.Run(async () =>
{
return await getDataTasks[0];
}).ContinueWith(this.CallBack, TaskContinuationOptions.ExecuteSynchronously);
Works: Using delegate handlers
// private members of controller
private delegate string DelegateHandler();
private string getDataFromAPISync()
{
return getDataFromAPI().GetAwaiter().GetResults();
}
private string getDataFromCacheSync()
{
return getDataFromCache().GetAwaiter().GetResults();
}
private void CallBack(IAsyncResult ar)
{
var apiResponse = apiInvoker.EndInvoke(ar);
writeToCacheTask(apiResponse);
}
// In controller
var apiInvoker = new DelegateHandler(this.getDataFromAPISync)
var cacheInvoker = new DelegateHandler(this.getDataFromCacheSync)
IAsyncResult apiResults = apiInvoker.BeginInvoke(this.CallBack, null);
IAsyncResult cacheResults = cacheInvoker.BeginInvoke(null, null);
var handles = new WaitHandle[]
{
apiResults.AsyncWaitHandle,
cacheResults.AsyncWaitHandle
}
WaitHandle.WaitAny(handles);
if (cacheResults.IsCompleted)
{
return cacheInvoker.EndInvoke(ar);
}
return apiInvoker.EndInvoke(ar);
When I am using delegate handler, it looks like the background thread used to make the api call, is used to handle the callback as well and only once callback is completed it is killed.
How to do the same using the Task library?
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.
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.
What I'm trying to do is create a 'Listener' which listens to several different Tcp ports at once, and pipes the messages to any Observers.
Pseudo-ish code:
private bool _Listen = false;
public void Start()
{
_Listen = true;
Task.Factory.StartNew(() => Listen(1);
Task.Factory.StartNew(() => Listen(2);
}
public void Stop()
{
_Listen = false;
}
private async void Listen(int port)
{
var tcp = new TcpClient();
while(_Listen)
{
await tcp.ConnectAsync(ip, port);
using (/*networkStream, BinaryReader, etc*/)
{
while(_Listen)
{
//Read from binary reader and OnNext to IObservable
}
}
}
}
(For brevity, I've omitted the try/catch inside the two whiles, both of which also check the flag)
My question is: should I be locking the flag, and if so, how does that tie-in with the async/await bits?
First of all, you should change your return type to Task, not void. async void methods are essentially fire-and-forget and can't be awaited or cancelled. They exist primarily to allow the creation of asynchronous event handlers or event-like code. They should never be used for normal asynchronous operations.
The TPL way to cooperatively cancel/abort/stop an asynchronous operation is to use a CancellationToken. You can check the token's IsCancellationRequested property to see if you need to cancel your operation and stop.
Even better, most asynchronous methods provided by the framework accept a CancellationToken so you can stop them immediatelly without waiting for them to return. You can use NetworkStream's ReadAsync(Byte[], Int32, Int32, CancellationToken) to read data and cancel immediatelly when someone calls your Stop method.
You could change your code to something like this:
CancellationTokenSource _source;
public void Start()
{
_source = new CancellationTokenSource();
Task.Factory.StartNew(() => Listen(1, _source.Token),_source.Token);
Task.Factory.StartNew(() => Listen(2, _source.Token), _source.Token);
}
public void Stop()
{
_source.Cancel();
}
private async Task Listen(int port,CancellationToken token)
{
var tcp = new TcpClient();
while(!token.IsCancellationRequested)
{
await tcp.ConnectAsync(ip, port);
using (var stream=tcp.GetStream())
{
...
try
{
await stream.ReadAsync(buffer, offset, count, token);
}
catch (OperationCanceledException ex)
{
//Handle Cancellation
}
...
}
}
}
You can read a lot more about cancellation in Cancellation in Managed Threads, including advice on how to poll, register a callback for cancellation, listen to multiple tokens etc.
The try/catch block exists because await throws an Exception if a Task is cancelled. You can avoid this by calling ContinueWith on the Task returned by ReadAsync and checking the IsCanceled flag:
private async Task Listen(int port,CancellationToken token)
{
var tcp = new TcpClient();
while(!token.IsCancellationRequested)
{
await tcp.ConnectAsync(ip, port);
using (var stream=tcp.GetStream())
{
///...
await stream.ReadAsync(buffer, offset, count, token)
.ContinueWith(t =>
{
if (t.IsCanceled)
{
//Do some cleanup?
}
else
{
//Process the buffer and send notifications
}
});
///...
}
}
}
await now awaits a simple Task that finishes when the continuation finishes
You would probably be better of sticking with RX all the way through instead of using Task. Here is some code I wrote for connecting to UDP sockets with RX.
public IObservable<UdpReceiveResult> StreamObserver
(int localPort, TimeSpan? timeout = null)
{
return Linq.Observable.Create<UdpReceiveResult>(observer =>
{
UdpClient client = new UdpClient(localPort);
var o = Linq.Observable.Defer(() => client.ReceiveAsync().ToObservable());
IDisposable subscription = null;
if ((timeout != null)) {
subscription = Linq.Observable.Timeout(o.Repeat(), timeout.Value).Subscribe(observer);
} else {
subscription = o.Repeat().Subscribe(observer);
}
return Disposable.Create(() =>
{
client.Close();
subscription.Dispose();
// Seems to take some time to close a socket so
// when we resubscribe there is an error. I
// really do NOT like this hack. TODO see if
// this can be improved
Thread.Sleep(TimeSpan.FromMilliseconds(200));
});
});
}
should I be locking the flag, and if so, how does that tie-in with the async/await bits?
You need to synchronize access to the flag somehow. If you don't, the compiler is allowed to make the following optimization:
bool compilerGeneratedLocal = _Listen;
while (compilerGeneratedLocal)
{
// body of the loop
}
Which would make your code wrong.
Some options how you can fix that:
Mark the bool flag volatile. This will ensure that the current value of the flag is always read.
Use CancellationToken (as suggested by Panagiotis Kanavos). This will make sure that the underlying flag is accessed in a thread-safe manner for you. It has also the advantage that many async methods support CancellationToken, so you can cancel them too.
Some form of Event (such as ManualResetEventSlim) would be a more obvious choice when you're potentially dealing with multiple threads.
private ManualResetEventSlim _Listen;
public void Start()
{
_Listen = new ManualResetEventSlim(true);
Task.Factory.StartNew(() => Listen(1);
Task.Factory.StartNew(() => Listen(2);
}
public void Stop()
{
_Listen.Reset();
}
private async void Listen(int port)
{
var tcp = new TcpClient();
while(_Listen.IsSet)
{
All, I have a long running process that I run on a background thread (with cancellation support) using the Task Paralell Library (TPL). The code for this long running taks is contained within Class Validation, and when the method
public bool AsyncRunValidationProcess(TaskScheduler _uiScheduler,
CancellationToken _token, dynamic _dynamic = null)
{
try
{
// Note: _uiScheduler is used to update the UI thread with progress infor etc.
for (int i = 0; i < someLargeLoopNumber; i++)
{
// Cancellation requested from UI Thread.
if (_token.IsCancellationRequested)
_token.ThrowIfCancellationRequested();
}
return true;
}
catch (Exception eX)
{
// Do stuff. Display `eX` if cancellation requested.
return false;
}
}
is run from Class Validation I can cancel the process fine. The cancellation request is handled by the appropriate delegate (shown below) and this works fine (I don't belive this is the cause of my problem).
When I run this method from another class, Class Batch, I do this via a "controller" method
asyncTask = Task.Factory.StartNew<bool>(() => asyncControlMethod(), token);
which in turn invokes the method
valForm.AsyncRunValidationProcess(uiScheduler, token,
new List<string>() { strCurrentSiteRelPath }));
where valForm is my accessor to Class Validation, the method runs fine, but when I attempt a cancellation the delegate
cancelHandler = delegate
{
UtilsTPL.CancelRunningProcess(asyncTask, cancelSource);
};
where
public static void CancelRunningProcess(Task _task,
CancellationTokenSource _cancelSource)
{
try
{
_cancelSource.Cancel();
_task.Wait(); // On cross-class call it freezes here.
}
catch (AggregateException aggEx)
{
if (aggEx.InnerException is OperationCanceledException)
Utils.InfoMsg("Operation cancelled at users request.");
if (aggEx.InnerException is SqlException)
Utils.ErrMsg(aggEx.Message);
}
}
freezes/hangs (with no unhandled exception etc.) on _task.Wait(). This (I belive - through testing) is to do with the fact that I am cancelling asyncControlMethod() which has called valForm.AsyncRunValidationProcess(...), so it is cancelling asyncControlMethod() which is causing the current process to hang. The problem seems to be with passing the CancellationTokenSource etc. to the child method. The IsCancellationPending event fires and kills the controlling method, which causes the child method to hang.
Can anyone tell me what I am doing wrong or (more pertinently), what should I be doing to allow such a cancellation procedure?
Note: I have tried to spawn a child task to run valForm.AsyncRunValidationProcess(...), with its own CancellationToken but this has not worked.
Thanks for your time.
The answer to this problem (helped massively by Jiaji Wu's comment and link) was that you cannot declare the CancellationToken as a global variable that is passed to the cascading methods; that is, you cannot have
public class MainClass
{
private CancellationTokenSource = source;
private CancellationToken token;
public MainClass()
{
source = new CancellationtokenSource();
token = source.Token;
}
private void buttonProcessSel_Click(object sender, EventArgs e)
{
// Spin-off MyMethod on background thread.
Task<bool> asyncControllerTask = null;
TaskSpin(asyncControllerTask, cancelSource, token, MyMethod);
}
private void method()
{
// Use the global token DOES NOT work!
if (token.IsCancellationRequested)
token.ThrowIfCancellationRequested();
}
private void TaskSpin(Task<bool> asyncTask, CancellationTokenSource cancelSource,
CancellationToken token, Func<bool> asyncMethod)
{
try
{
token = cancelSource.Token;
asyncTask = Task.Factory.StartNew<bool>(() => asyncMethod(token), token);
// To facilitate multitasking the cancelTask ToolStripButton
EventHandler cancelHandler = null;
if (cancelSource != null)
{
cancelHandler = delegate
{
UtilsTPL.CancelRunningProcess(mainForm, uiScheduler, asyncTask, cancelSource, true);
};
}
// Callback for finish/cancellation.
asyncTask.ContinueWith(task =>
{
// Handle cancellation etc.
}
// Other stuff...
}
}
}
Use of the global token in the maethod run on the background thread doen NOT work! The method must be explicitly passed the token for it to be able to register it. I am not sure of the exact reason why this is the case, but I will know in future, now you need to pass the token to MyMethod() like this
private void method(CancellationToken token)
{
// Use the global token DOES NOT work!
if (token.IsCancellationRequested)
token.ThrowIfCancellationRequested();
}
I hope this helps someone else.