mad*_*dd0 1 .net c# async-await winrt-xaml
我正在尝试创建一个控件,该控件公开DoLoading
消费者可以订阅的事件以执行加载操作.为方便起见,事件处理程序应该从UI线程使消费者能够随意更新UI叫,但他们也将能够使用异步/等待对该不会阻塞UI线程中执行长时间运行的任务.
为此,我宣布了以下代表:
public delegate Task AsyncEventHandler<TEventArgs>(object sender, TEventArgs e);
Run Code Online (Sandbox Code Playgroud)
这允许消费者订阅该活动:
public event AsyncEventHandler<bool> DoLoading;
Run Code Online (Sandbox Code Playgroud)
这个想法是消费者将订阅事件(这条线在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
}
};
Run Code Online (Sandbox Code Playgroud)
在适当的时间点,我正在获取TaskScheduler
UI线程并存储它_uiScheduler
.
适当时由loader
以下行触发事件(这发生在随机线程中):
this.PerformLoadingActionAsync().ContinueWith(
_ =>
{
// Other operations that must happen on UI thread
},
_uiScheduler);
Run Code Online (Sandbox Code Playgroud)
请注意,此行不是从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);
}
}
Run Code Online (Sandbox Code Playgroud)
正如你所看到的,我正在开始执行两项任务,并希望我ContinueWith
之前完成所有这些任务.
在commandTask
完成立即,所以可以暂时忽略.的eventHandlerTask
,在我看来,应该只完成一个事件处理程序完成的,因为我在等待呼叫调用的事件处理程序的方法,我在等待事件处理程序本身.
然而,实际发生的是,只要await Task.Delay(1000)
我的事件处理程序中的行被执行,任务就会完成.
为什么这样,我怎么能得到我期望的行为?
你正确地意识到在这种情况下StartNew()
返回Task<Task>
,你关心内部Task
(虽然我不确定你为什么Task
在开始之前等待外部commandTask
).
但是你回来Task<Task>
并忽略内心Task
.你应该做的是使用await
替代return
和改变的返回类型PerformLoadingActionAsync()
只是Task
:
await Task.WhenAll(evenHandlerTask, commandTask);
Run Code Online (Sandbox Code Playgroud)
几个笔记:
使用事件处理程序这种方式非常危险,因为您关心Task
从处理程序返回的内容,但如果有更多处理程序,则只有在Task
正常引发事件时才会返回最后一个处理程序.如果你真的想这样做,你应该调用GetInvocationList()
,它允许你分别调用和await
处理每个处理程序:
private async Task OnDoLoading(bool mustLoadPreviousRunningState)
{
var handler = this.DoLoading;
if (handler != null)
{
var handlers = handler.GetInvocationList();
foreach (AsyncEventHandler<bool> innerHandler in handlers)
{
await innerHandler(this, mustLoadPreviousRunningState);
}
}
}
Run Code Online (Sandbox Code Playgroud)
如果您知道您将永远不会有多个处理程序,则可以使用可以直接设置而不是事件的委托属性.
如果你有一个async
方法或lambda只有await
它之前return
(并且没有finally
s),那么你不需要创建它async
,只需Task
直接返回:
Task.Factory.StartNew(() => this.OnDoLoading(true))
Run Code Online (Sandbox Code Playgroud) 归档时间: |
|
查看次数: |
823 次 |
最近记录: |