Given the following code:
public delegate Task AsyncEventHandler<in TEventArgs>(object sender, TEventArgs eventArgs);
public static Task InvokeAsync(this AsyncEventHandler eventHandler, object sender, EventArgs eventArgs)
{
if (eventHandler == null) return Task.CompletedTask;
var delegates = eventHandler.GetInvocationList().Cast<AsyncEventHandler>();
var tasks = delegates.Select(it => it.Invoke(sender, eventArgs));
return Task.WhenAll(tasks);
}
I have a test function whereby the faulty handlers should throw exceptions, and the workinghanlder should run - currently only the first FaultyHandler1 is called and no others event handlers.
private class NonGenericNotifier
{
public event AsyncEventHandler SomethingHappened;
public Task OnSomethingHappening() => SomethingHappened.InvokeAsync(this, EventArgs.Empty);
}
public async Task Multiple_Exceptions_That_Occur_During_Event_Handling_Should_Be_Propagated()
{
var isHandler1Called = false;
var isHandler2Called = false;
var isWorkingHandlerCalled = false;
var notifier = new NonGenericNotifier();
Task FaultyHandler1(object sender, EventArgs eventArgs)
{
isHandler1Called = true;
throw new InvalidOperationException();
}
Task FaultyHandler2(object sender, EventArgs eventArgs)
{
isHandler2Called = true;
throw new InvalidOperationException();
}
Task WorkingHandler(object sender, EventArgs eventArgs)
{
isWorkingHandlerCalled = true;
return Task.CompletedTask;
}
notifier.SomethingHappened += FaultyHandler1;
notifier.SomethingHappened += FaultyHandler2;
notifier.SomethingHappened += WorkingHandler;
await Should.ThrowAsync<InvalidOperationException>(async () => await notifier.OnSomethingHappening());
isHandler1Called.ShouldBe(true);
isHandler2Called.ShouldBe(true);
isWorkingHandlerCalled.ShouldBe(true);
}
Assuming a single exception can be thrown I beleive this should be an AggregateException containing an exception for each Task, and most importantly the InvokeAsync method above should bail on the first exception encountered.
I have started to create a List<Exception> within the InvokeAsync extension method, and wrap each it => it.Invoke(sender, eventArgs) with a try/catch construct and within the catch add the exception to the exception list.
However I am lost on how to collate this list of exceptions and then send on as an AggregateException.
UPDATE (FIX?)
Thanks to Artur for pointing me in the right direction. I changed the InvokeAsync extension method to the below, and it works - no longer halting on the first task. We have gone from var tasks = delegates.Select(it => it.Invoke(sender, eventArgs)); to the below using the code here:
public static Task InvokeAsync(this AsyncEventHandler eventHandler, object sender, EventArgs eventArgs)
{
if (eventHandler == null) return Task.CompletedTask;
var delegates = eventHandler.GetInvocationList().Cast<AsyncEventHandler>();
var tasks = delegates.Select(async it => await it.Invoke(sender, eventArgs));
return Task.WhenAll(tasks).WithAggregatedExceptions();
}
static Task WithAggregatedExceptions(this Task task)
{
// using AggregateException.Flatten as a bonus
return task.ContinueWith(
continuationFunction: priorTask =>
priorTask.IsFaulted &&
priorTask.Exception is AggregateException ex && (ex.InnerExceptions.Count > 1 || ex.InnerException is AggregateException) ? Task.FromException(ex.Flatten()) : priorTask,
cancellationToken: CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously,
scheduler: TaskScheduler.Default).Unwrap();
}
My issue is subscribers of this event, writing synchronous handlers over which I have no control - this would stop the other event handlers (sync and async) running that are attached to the same event.
I also appreciate this is the designed function of Task.WhenAll if you were mixing async and non-async handlers... if there is one reason to not write synchronous code in an async function without await Task.Yield() this is it.
Question
Can we say that wrapping the delegates.Select(async it => await it.Invoke(sender, eventArgs) with async/await allows synchronous method to run, and at worst(?) wrap twice an async method (which is the same as nesting async/await function calls) so is actually a non-issue?
Are there any side effects that have been introduced?
With the bounty looking for authorative guidance on how this would be implemented, one answer (much appreciated for contributing to the discussion) says to avoid async events, yet in other places like the discord c# client they have embraced async events (with timeout wrappers etc).
Task.WhenAll will invoke all the handlers when it reifies its parameter. It will invoke them one at a time, and then will asynchronously wait for all the tasks to complete.
The reason you were seeing the halting on the first exception was because the exception was thrown during reification. It's normal for asynchronous (Task-returning) functions to place any exceptions on the returned task. It's abnormal for asynchronous functions to throw exceptions directly.
So, this is the problematic code:
Task FaultyHandler1(object sender, EventArgs eventArgs)
{
isHandler1Called = true;
throw new InvalidOperationException();
}
One of these would be correct:
async Task FaultyHandler1(object sender, EventArgs eventArgs)
{
isHandler1Called = true;
throw new InvalidOperationException();
}
Task FaultyHandler1(object sender, EventArgs eventArgs)
{
isHandler1Called = true;
return Task.FromException(new InvalidOperationException());
}
You're seeing the odd behavior because the asynchronous handler is misbehaving (by throwing a synchronous exception).
Now, if you want to allow misbehaving asynchronous handlers, you can do that, either with an explicit try/catch or the extra async/await:
var tasks = delegates.Select(it => try { return it.Invoke(sender, eventArgs); } catch (Exception ex) { return Task.FromException(ex); });
// async/await is necessary to handle misbehaving asynchronous handlers that throw synchronous exceptions
var tasks = delegates.Select(async it => await it.Invoke(sender, eventArgs));
If you do keep the async/await approach, please do comment it, because coding constructs like that are often assumed to be spurious and may be removed by a future maintainer.
The WithAggregatedExceptions looks fine as-is, but it can be simplified if you want:
static async Task WithAggregatedExceptions(this Task task)
{
try { await task; }
catch { throw task.Exception; }
}
My issue is subscribers of this event, writing synchronous handlers over which I have no control - this would stop the other event handlers (sync and async) running that are attached to the same event.
Well, yes. Task.WhenAll reifies its collection of tasks synchronously, which invokes all the handlers one at a time.
If you want to allow synchronous handlers as well as asynchronous ones all at the same time, you can wrap the invocations in a Task.Run:
var tasks = delegates.Select(it => Task.Run(() => it.Invoke(sender, eventArgs)));
Can we say that wrapping the delegates.Select(async it => await it.Invoke(sender, eventArgs) with async/await allows synchronous method to run, and at worst(?) wrap twice an async method (which is the same as nesting async/await function calls) so is actually a non-issue?
The extra async/await for asynchronous handlers is a non-issue; it's very slightly less efficient, and appears unnecessary, so I'd say it's in danger of being removed (unless commented). It doesn't "allow synchronous methods to run"; instead, it corrects the misbehaving methods that throw exceptions directly instead of placing them on the returned Task as expected.
Are there any side effects that have been introduced?
Not really. If you do use the Task.Run approach, then all the handlers are invoked on thread pool threads and may run concurrently, which may be surprising.
one answer (much appreciated for contributing to the discussion) says to avoid async events, yet in other places like the discord c# client they have embraced async events (with timeout wrappers etc).
I am 100% in agreement with that answer.
Here's how I think about it:
The Observer pattern is a way to notify observers of state changes. The Observer pattern is a clear fit for "events" in OOP: any number of observers may subscribe to state change notifications. This is what C# events were patterned after: notifying subscribers of things. There's no mechanism for information to "flow back". While the language allows C# events with return values, it's not natural by any means. The same limitation happens with exceptions (which can be considered a kind of return): the standard handler?.Invoke pattern starts to break (stopping invocations at the first exception, etc).
As soon as you have information "flowing back" (including needing to handle exceptions, or needing to await all the handlers to complete), you're no longer in the Observer pattern, and you are no longer in the happy path of C# events.
Generally, I find most "events" of this type are usually related to a Strategy or Visitor pattern, rather than Observer. Neither Strategy nor Visitor are good fits for C# events, although they are often (sadly) implemented that way. I consider that a common design mistake (for all OOP languages).
In my opinion, the design of using C# events in an async way is not robust, and it will always behave in a slightly uncontrolled way. There are better techniques to make event processing robust.
One of the best such technologies is TPL Dataflows (https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/dataflow-task-parallel-library). This library allows you to program stream processing in a very controlled way, it helps you deal with task schedulers etc. Once you apply this successfully, all the problems in your question will be addressed.
There are obviously other alternatives out there, but I would clearly abstain from re-implementing this by using C# events....
Related
I want to convert a (relatively) slow event handler to async to avoid blocking the other event subscribers. In the example below I do not want the other subscribers to the Message event to have to wait.
The only thing I can think of looks like "cheating":
client.Message += async (object sender, MessageEventArgs e) => {
await Task.Run(() => { });
logger.Info("Some info {0}", e.Message);
}
Of course I could wrap the whole logger.Info call in the Task, but I don't see why that is better. I just looks less like cheating.
My only other thought is that the handler should be left synchronous, and the Message event should only be subscribed to when latency is not critical. So I could have a second event that is only used by low latency async subscribers.
I am looking for feedback on the proper coding design here.
You can use Task.Yield if you want to attach an asynchronous handler that will be invoked after all synchronous handlers of the same event have been invoked, and you also want the asynchronous invocation to occur on the current synchronization context. For a GUI application this means that you want the handler to run on the UI thread.
client.Message += async (sender, e) =>
{
await Task.Yield();
logger.Info("Some info {0}", e.Message);
}
Don't use await Task.Run(() => { }), because it's just a verbose, idiomatic, less efficient and (possibly) less robust alternative of await Task.Yield().
If you want to attach an asynchronous handler that will run on a ThreadPool thread (instead of the current synchronization context), use Task.Run:
client.Message += async (sender, e) =>
{
await Task.Run(() => logger.Info("Some info {0}", e.Message));
}
You should only do that if you are sure that the logger object is thread-safe, supports concurrent operations, and doesn't require thread-affinity (like many COM components do).
I need help identifying a generic way of catching exceptions that are thrown from within a delegate when that delegate contains a Task.Run that is throwing an exception. Currently, when I invoke a delegate (in this case a func) that is passed into another "master" method and an exception is thrown from within a Task.Run in the delegate, the exception is not caught with a try/catch in the code surrounding the invocation. The following simplified example shows the exceptions inside the delegate being caught by the try/catch inside the delegate but being "lost" elsewhere:
public static void Main()
{
AppDomain.CurrentDomain.UnhandledException += (s, e) => CurrentDomain_UnhandledException();
TaskScheduler.UnobservedTaskException += (s, e) => CurrentDomain_UnhandledException();
HandledDelegate(() => Task.Run(() =>
{
try
{
Console.WriteLine("Executed");
throw new Exception($"Caught");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
throw new Exception($"Caught Again");
}
}
));
}
private static void CurrentDomain_UnhandledException()
{
Console.WriteLine("Unhandled Exception Occurred.");
}
public static void HandledDelegate(Action a)
{
try
{
a.Invoke();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
throw;
}
Thread.Sleep(10000);
}
And the following example shows the exception not being caught within the try/catch around the invoke, and bubbling up to the app domain as an unhandled exception.
public static void Main()
{
AppDomain.CurrentDomain.UnhandledException += (s, e) => Console.WriteLine("Unhandled Exception");
HandleDelegateEx(async () => await Task.Run(() => throw new Exception("test")));
Thread.Sleep(1000);
}
public static void HandleDelegateEx(Action a)
{
try
{
a.Invoke();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
This last one is most like my current code which I need to fix. The delegates often Task the code off on a background thread and don't handle any errors (expecting them to be caught by where the delegate is invoked.
This code is used throughout the app in hundreds of locations, and few, if any, catch exceptions within the delegate (as other programmers erroneously thought that exceptions thrown by the delegate would be caught). Is there any way to catch exceptions thrown from inside these delegates without massively refactoring each and every delegate to catch exceptions?
The issue is not connected to delegate itself. In your fiddle you don't wait for Task returned by delegate to complete. Basically the delegate will return Task and the method will continue. In your original code in question:
try
{
// Execute the requested action.
retval = clientOperation.Invoke(remoteObj);
}
catch(ex)
{
// log here.
}
finally
{
CloseChannel(remoteObj);
}
It will jump straight to finally clause and attempt to close connection. You need to handle Func's returning tasks separately:
public void HandleDelegateEx<T>(Func<T> a)
{
try
{
a.Invoke();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
public async Task HandleDelegateExAsync<T>(Func<Task<T>> a)
{
try
{
await a.Invoke();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
HandleDelegateEx<int>(() => throw new Exception("test")); // prints "test"
Func<Task<int>> func = () => Task.FromException<int>(new Exception("test async"));
HandleDelegateEx(func); // does not print anything
await HandleDelegateExAsync(func); // prints "test async"
You can get the same behavior without delegates:
Task task = null;
try
{
task = Task.FromException(new Exception("test async"));
}
catch (Exception ex)
{
Console.WriteLine(ex.Message); // does not go here
}
await task; // throws here
In order for your exceptions to be observed, you would either have to await the returned task as Guru Stron suggests (implying a massive refactoring I guess). Or somehow adding Wait() to the result of the Task.Run() call, but this will block the calling thread (I'm assuming this is the UI thread) while the background thread churns the delegate - neglecting the benefit of the task being a background thread in the first place.
So, most probably, here is the crux of the problem: you need to wait for the background Task to finish so you can observe any possible exceptions, without blocking the UI thread, and do this while modifying the least amount of code.
I'm assuming your application is either Windows Forms or WPF.
For the first problem -waiting for the task to finish without blocking the UI thread-, I would turn to the old (and very frowned upon) Application.DoEvents() (or its equivalent in WPF, second answer). This is strongly discouraged these days, but I guess it is much better than ignoring exceptions that should have been handled (and keep in mind this was valid and in fact, the only way of keeping a responsive UI in the 16-bit era).
Instead of adding Wait(), to the background task, I would wait for its completion with the following loop:
while( !backgroundTask.IsCompleted ) {
Application.DoEvents(); // keep UI responsive while background task finishes
}
Now, the second problem, we would have to somehow inject this loop right after the background task is queued in the thread pool, and as you describe, this occurs in potentially hundreds of places.
Here I would try using Harmony, a very powerful dynamic IL patching tool. This tool allows you to target a method and piggyback a prefix and/or a postfix method. The prefix method will be called automatically before the target method is called, and the postfix method will be called after the target method is called.
The patching would look something like this:
// Must be called once before the target delegates are executed
private void MyClass.Patch() {
var original = typeof(Task).GetMethod("Run", ...); // I'm omitting the code that would select the (Action action) overload
var postfix = typeof(MyClass).GetMethod("WaitWithResponsiveUI", ...);
harmony.Patch(original, postfix: new HarmonyMethod(postfix));
}
private void MyClass.WaitWithResponsiveUI(ref Action __result) {
// __result contains the original Task returned by Task.Run()
while( !__result.IsCompleted ) {
Application.DoEvents(); // keep UI responsive while background task finishes
}
__result.Wait(); // Any exceptions produced inside the delegate should be rethrown at this moment, and they should be trappable by the code that invoked the delegate
}
Of course, this has the disadvantage that every Task.Run(Action action) called after patching would now wait until the task is done, globally, and this might not be desirable for every call.
EDIT: In defense of Application.DoEvents();
Here is a very well written answer detailing all the potential pitfalls of using Application.DoEvents(). These pitfalls are real and should be considered.
However, one aspect I strongly disagree is the assertion that the async-await mechanism is a safe way to avoid all these problems. That is simply not true.
When a UI event handler finds an await, it basically sets up a continuation that will be able to resume the event handling in the future, and immediately returns, giving the application's message pump a chance to continue running (basically what Application.DoEvents(), with the difference that the latter will return once the event queue is empty).
When the awaited operation completes, the continuation IS enqueued as another event into the Message event queue, so it won't resume execution until the message pump has handled all the events that have been posted before. My point being - While the operation is awaited, messages ARE being processed and you still need to protect your application from any reentrancy pitfalls manually. See here: Handling Reentrancy in Async Apps (C#).
Another pitfall exclusive to async-await UI code is that some components (DataGridView comes to mind) pass some of their event arguments to their handlers in objects that implement IDisposable. The component raises the event, and disposes the arguments as soon as the event handler returns. In async code, this return to the caller will happen when the first await in the handler code is reached. This has the effect that when the awaited code resumes, accessing the event-arguments object throws an ObjectDisposedException, as it has already been disposed by the raising component itself!
The "false multithreading" aspect is already being handled here, as the work is truly being offloaded to the thread pool.
So, in my opinion, the only real problem to address is the potential stack-overflow problem, that could be addressed by reentrancy avoidance measures that have to be accounted for in the first place, whether you use async/await or DoEvents().
Partly as an exercise in exploring async, I though I'd try creating a ServiceBrokerWatcher class. The idea is much the same as a FileSystemWatcher - watch a resource and raise an event when something happens. I was hoping to do this with async rather than actually creating a thread, because the nature of the beast means that most of the time it is just waiting on a SQL waitfor (receive ...) statement. This seemed like an ideal use of async.
I have written code which "works", in that when I send a message through broker, the class notices it and fires off the appropriate event. I thought this was super neat.
But I suspect I have gotten something fundamentally wrong somewhere in my understanding of what is going on, because when I try to stop the watcher it doesn't behave as I expect.
First a brief overview of the components, and then the actual code:
I have a stored procedure which issues a waitfor (receive...) and returns a result set to the client when a message is received.
There is a Dictionary<string, EventHandler> which maps message type names (in the result set) to the appropriate event handler. For simplicity I only have the one message type in the example.
The watcher class has an async method which loops "forever" (until cancellation is requested), which contains the execution of the procedure and the raising of the events.
So, what's the problem? Well, I tried hosting my class in a simple winforms application, and when I hit a button to call the StopListening() method (see below), execution isn't cancelled right away as I thought it would be. The line listener?.Wait(10000) will in fact wait for 10 seconds (or however long I set the timeout). If I watch what happens with SQL profiler I can see that the attention event is being sent "straight away", but still the function does not exit.
I have added comments to the code starting with "!" where I suspect I have misunderstood something.
So, main question: Why isn't my ListenAsync method "honoring" my cancellation request?
Additionally, am I right in thinking that this program is (most of the time) consuming only one thread? Have I done anything dangerous?
Code follows, I tried to cut it down as much as I could:
// class members //////////////////////
private readonly SqlConnection sqlConnection;
private CancellationTokenSource cts;
private readonly CancellationToken ct;
private Task listener;
private readonly Dictionary<string, EventHandler> map;
public void StartListening()
{
if (listener == null)
{
cts = new CancellationTokenSource();
ct = cts.Token;
// !I suspect assigning the result of the method to a Task is wrong somehow...
listener = ListenAsync(ct);
}
}
public void StopListening()
{
try
{
cts.Cancel();
listener?.Wait(10000); // !waits the whole 10 seconds for some reason
} catch (Exception) {
// trap the exception sql will raise when execution is cancelled
} finally
{
listener = null;
}
}
private async Task ListenAsync(CancellationToken ct)
{
using (SqlCommand cmd = new SqlCommand("events.dequeue_target", sqlConnection))
using (CancellationTokenRegistration ctr = ct.Register(cmd.Cancel)) // !necessary?
{
cmd.CommandTimeout = 0;
while (!ct.IsCancellationRequested)
{
var events = new List<string>();
using (var rdr = await cmd.ExecuteReaderAsync(ct))
{
while (rdr.Read())
{
events.Add(rdr.GetString(rdr.GetOrdinal("message_type_name")));
}
}
foreach (var handler in events.Join(map, e => e, m => m.Key, (e, m) => m.Value))
{
if (handler != null && !ct.IsCancellationRequested)
{
handler(this, null);
}
}
}
}
}
You don't show how you've bound it to the WinForms app, but if you are using regular void button1click methods, you may be running into this issue.
So your code will run fine in a console app (it does when I try it) but deadlock when called via the UI thread.
I'd suggest changing your controller class to expose async start and stop methods, and call them via e.g.:
private async void btStart_Click(object sender, EventArgs e)
{
await controller.StartListeningAsync();
}
private async void btStop_Click(object sender, EventArgs e)
{
await controller.StopListeningAsync();
}
Peter had the right answer. I was confused for several minutes about what was deadlocking, but then I had my forehead slapping moment. It is the continuation of ListenAsync after the ExecuteReaderAsync is cancelled, because it's just a task, not a thread of its own. That was, after all, the whole point!
Then I wondered... OK, what if I tell the async part of ListenAsync() that it doesn't need the UI thread. I will call ExecuteReaderAsync(ct) with .ConfigureAwait(false)! Aha! Now the class methods don't have to be async anymore, because in StopListening() I can just listener.Wait(10000), the wait will continue the task internally on a different thread, and the consumer is none the wiser. Oh boy, so clever.
But no, I can't do that. Not in a webforms application at least. If I do that then the textbox is not updated. And the reason for that seems clear enough: the guts of ListenAsync invoke an event handler, and that event handler is a function which wants to update text in a textbox - which no doubt has to happen on the UI thread. So it doesn't deadlock, but it also can't update the UI. If I set a breakpoint in the handler which wants to update the UI the line of code is hit, but the UI can't be changed.
So in the end it seems the only solution in this case is indeed to "go async all the way down". Or in this case, up!
I was hoping that I didn't have to do that. The fact that the internals of my Watcher are using async methodologies rather than just spawning a thread is, in my mind, an "implementation detail" that the caller shouldn't have to care about. But a FileSystemWatcher has exactly the same issue (the need to control.Invoke if you want to update a GUI based on a watcher event), so that's not so bad. If I was a consumer that had to choose between using async or using Invoke, I'd choose async!
I have upgraded a .NET 4.0 WinForms program to .NET 4.5.1 in the hope of using the new await on async WCF calls in order to prevent freezing the UI while waiting for data (the original was quickly written so I was hoping the old synchronous WCF calls could be made async with minimal change to existing code using the new await feature).
From what I understand, await was supposed to return to the UI thread with no extra coding, but for some reason it does not for me, so the following would give the cross thread exception:
private async void button_Click(object sender, EventArgs e)
{
using (MyService.MyWCFClient myClient = MyServiceConnectFactory.GetForUser())
{
var list=await myClient.GetListAsync();
dataGrid.DataSource=list; // fails if not on UI thread
}
}
Following the article await anything I made a custom awaiter so I could issue an await this to get back on the UI thread, which solved the exception, but then I found my UI was still frozen despite using the asynchronously Tasks generated by Visual Studio 2013 for my WCF service.
Now the program is actually a Hydra VisualPlugin running inside an old Delphi application, so if anything could mess things up, is probably happens... But does anybody have any experience with what exactly could make awaiting an async WCF not returning to the UI thread or hang the UI thread?
Maybe upgrading from 4.0 to 4.5.1 makes the program miss some reference to do the magic?
Now while I would like to understand why await does not work as advertised, I ended up with making my own workaround: A custom awaiter that forces a task to run in a background thread, and which forces the continuation to arrive back on the UI thread.
Similarly to .ConfigureAwait(false) I wrote an .RunWithReturnToUIThread(this) extension for Taks as follows:
public static RunWithReturnToUIThreadAwaiter<T> RunWithReturnToUIThread<T>(this Task<T> task, Control control)
{
return new RunWithReturnToUIThreadAwaiter<T>(task, control);
}
public class RunWithReturnToUIThreadAwaiter<T> : INotifyCompletion
{
private readonly Control m_control;
private readonly Task<T> m_task;
private T m_result;
private bool m_hasResult=false;
private ExceptionDispatchInfo m_ex=null; // Exception
public RunWithReturnToUIThreadAwaiter(Task<T> task, Control control)
{
if (task == null) throw new ArgumentNullException("task");
if (control == null) throw new ArgumentNullException("control");
m_task = task;
m_control = control;
}
public RunWithReturnToUIThreadAwaiter<T> GetAwaiter() { return this; }
public bool IsCompleted
{
get
{
return !m_control.InvokeRequired && m_task.IsCompleted; // never skip the OnCompleted event if invoke is required to get back on UI thread
}
}
public void OnCompleted(Action continuation)
{
// note to self: OnCompleted is not an event - it is called to specify WHAT should be continued with ONCE the result is ready, so this would be the place to launch stuff async that ends with doing "continuation":
Task.Run(async () =>
{
try
{
m_result = await m_task.ConfigureAwait(false); // await doing the actual work
m_hasResult = true;
}
catch (Exception ex)
{
m_ex = ExceptionDispatchInfo.Capture(ex); // remember exception
}
finally
{
m_control.BeginInvoke(continuation); // give control back to continue on UI thread even if ended in exception
}
});
}
public T GetResult()
{
if (m_ex == null)
{
if (m_hasResult)
return m_result;
else
return m_task.Result; // if IsCompleted returned true then OnCompleted was never run, so get the result here
}
else
{ // if ended in exception, rethrow it
m_ex.Throw();
throw m_ex.SourceException; // just to avoid compiler warning - the above does the work
}
}
}
Now in the above I am not sure if my exception handling is needed like this, or if the Task.Run really need to use async and await in its code, or if the multiple layers of Tasks could give problems (I am basically bypassing the encapsulated Task's own method of returning - since it did not return correctly in my program for WCF services).
Any comments/ideas regarding efficiency of the above workaround, or what caused the problems to begin with?
Now the program is actually a Hydra VisualPlugin running inside an old Delphi application
That's probably the problem. As I explain in my async intro blog post, when you await an Task and that task is incomplete, the await operator by default will capture a "current context" and later resume the async method in that context. The "current context" is SynchronizationContext.Current unless it is null, in which case it is TaskScheduler.Current.
So, the normal "return to UI thread" behavior is a result of await capturing the UI synchronization context - in the case of WinForms, a WinFormsSynchronizationContext.
In a normal WinForms application, SynchronizationContext.Current is set to a WinFormsSynchronizationContext the first time you create a Control. Unfortunately, this does not always happen in plugin architectures (I've seen similar behavior on Microsoft Office plugins). I suspect that when your code awaits, SynchronizationContext.Current is null and TaskScheduler.Current is TaskScheduler.Default (i.e., the thread pool task scheduler).
So, the first thing I'd try is creating a Control:
void EnsureProperSynchronizationContext()
{
if (SynchronizationContext.Current == null)
var _ = new Control();
}
Hopefully, you would only have to do that once, when your plugin is first invoked. But you may have to do it at the beginning of all your methods that can be invoked by the host.
If that doesn't work, you can create your own SynchronizationContext, but it's best to use the WinForms one if you can. A custom awaiter is also possible (and if you go that route, it's easier to wrap TaskAwaiter<T> rather than Task<T>), but the disadvantage of a custom awaiter is that is has to go on every await.
Imagine a WPF code-behind event handler:
<Button Click="OnButtonClick" />
In C# 4 you would declare your handler as:
private void OnButtonClick(object sender, RoutedEventArgs e) { ... }
In C# 5 you can declare an async handler
private async void OnButtonClick(object sender, RoutedEventArgs e) { ... }
So what is WPF doing with this? A few minutes of searching about didn't turn anything up.
It seems that it's possible to perform UI updates after await statements. Does this imply that the task is continued on the Dispatcher thread?
If the Task raised an error, would it be raised through the WPF Dispatcher, or only via the TaskScheduler?
Are there any other interesting aspects to this that might be nice to understand?
You may find my async/await intro helpful.
An async method is re-written by the compiler to support the await operator. Every async method starts out synchronous (in this case, on the UI thread) until it awaits some operation (that is not already completed).
By default, the context is saved, and when the operation completes, the rest of the method is scheduled to execute in that context. The "context" here is SynchronizationContext.Current unless it is null, in which case it is TaskScheduler.Current. As Drew pointed out, WPF provides a DispatcherSynchronizationContext which is tied to the WPF Dispatcher.
Regarding error handling:
When you await a Task inside a WPF async void event handler, the error handling goes like this:
The Task completes with an error. The exception is wrapped into an AggregateException, like all Task errors.
The await operator sees that the Task completed with an error. It unwraps the original exception and re-throws it, preserving the original stack trace.
The async void method builder catches the exception escaping from an async void method and passes it to the SynchronizationContext that was active when the async void method started executing (in this case, the same WPF context).
The exception is raised (with the original stack trace, and without any annoying AggregateException wrapping) on the Dispatcher.
This is rather convoluted, but the intent is to have exceptions raised from async event handlers be practically the same as exceptions raised from regular event handlers.
A partial answer. From MSDN:
An async method that has a void return type can’t be awaited, and the caller of a void-returning method can't catch any exceptions that the method throws.
So any errors would only be available via the TaskScheduler.
Also, there's nothing XAML-specific going on with the event handler registration. It could have been done in code:
this.button.Click += OnButtonClick;
Or even as an async lambda:
this.button.Click += async (s,e) => { ... };
As for safety of UI updates after an await, it seems that the continuation is executed within SynchronisationContext.Current, which is set per thread. In WPF, this is a DispatcherSynchronisationContext that's coupled to the WPF Dispatcher that pumped the event in the first place.