我正在尝试创建一个控件,该控件公开DoLoading
消费者可以订阅的事件以执行加载操作。为方便起见,应该从 UI 线程调用事件处理程序,允许消费者随意更新 UI,但他们也可以使用 async/await 来执行长时间运行的任务,而不会阻塞 UI 线程。
为此,我宣布了以下代表:
public delegate Task AsyncEventHandler<TEventArgs>(object sender, TEventArgs e);
这允许消费者订阅事件:
public event AsyncEventHandler<bool> DoLoading;
这个想法是消费者将订阅事件(这一行在 UI 线程中执行):
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
}
};
在适当的时间点,我得到一个TaskScheduler
UI 线程并将其存储在_uiScheduler
.
loader
该事件在适当时由以下行触发(这发生在随机线程中):
this.PerformLoadingActionAsync().ContinueWith(
_ =>
{
// Other operations that must happen on UI thread
},
_uiScheduler);
请注意,此行不是从 UI 线程调用的,而是需要在加载完成时更新 UI,所以我使用ContinueWith
在加载任务完成时在 UI 任务调度程序上执行代码。
我尝试了以下方法的几种变体,但都没有奏效,所以这就是我所在的位置:
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);
}
}
如您所见,我正在开始两项任务,并希望我ContinueWith
之前的任务能够完成所有任务。
立即完成,因此commandTask
暂时可以忽略它。在eventHandlerTask
我看来,应该只完成事件处理程序完成的一项,因为我正在等待对调用事件处理程序的方法的调用并且我正在等待事件处理程序本身。
然而,实际发生的是,只要await Task.Delay(1000)
我的事件处理程序中的行执行,任务就会完成。
为什么会这样,我怎样才能得到我期望的行为?