I'm trying to create a control that exposes a DoLoading
event that consumers can subscribe to in order to perform loading operations. For convenience, event handlers should be called from the UI thread allowing consumers to update the UI at will, but they will also be able to use async/await to perform long-running tasks without blocking the UI thread.
For this, I have declared the following delegate:
public delegate Task AsyncEventHandler<TEventArgs>(object sender, TEventArgs e);
That allows consumers to subscribe to the event:
public event AsyncEventHandler<bool> DoLoading;
The idea is that consumers will subscribe to the event as so (this line is executed in the UI thread):
loader.DoLoading += async (s, e) =>
{
for (var i = 5; i > 0; i--)
{
loader.Text = i.ToString(); // UI update
await Task.Delay(1000); // long-running task doesn't block UI
}
};
At an appropriate point in time, I'm getting a TaskScheduler
for the UI thread and storing it in _uiScheduler
.
The event is triggered when appropriate by the loader
with the following line (this happens in a random thread):
this.PerformLoadingActionAsync().ContinueWith(
_ =>
{
// Other operations that must happen on UI thread
},
_uiScheduler);
Notice that this line is not called from the UI thread but needs to update the UI when loading is completed, so I'm using ContinueWith
to execute code on the UI task scheduler when the loading task completes.
I've tried several variations of the following methods, none of which have worked, so here's where I'm at:
private async Task<Task> PerformLoadingActionAsync()
{
TaskFactory uiFactory = new TaskFactory(_uiScheduler);
// Trigger event on the UI thread and await its execution
Task evenHandlerTask = await uiFactory.StartNew(async () => await this.OnDoLoading(_mustLoadPreviousRunningState));
// This can be ignored for now as it completes immediately
Task commandTask = Task.Run(() => this.ExecuteCommand());
return Task.WhenAll(evenHandlerTask, commandTask);
}
private async Task OnDoLoading(bool mustLoadPreviousRunningState)
{
var handler = this.DoLoading;
if (handler != null)
{
await handler(this, mustLoadPreviousRunningState);
}
}
As you can see, I'm starting two tasks and expect my ContinueWith
from before to execute one all of them complete.
The commandTask
completes immediately, so it can be ignored for the moment. The eventHandlerTask
, as I see it, should only complete one the event handler completes, given that I'm awaiting the call to the method that calls the event handler and I'm awaiting the event handler itself.
However, what's actually happening, is that the tasks are being completed as soon as the line await Task.Delay(1000)
in my event handler is executed.
Why is this and how can I get the behaviour I expect?
See Question&Answers more detail:
os