我们有一个事件聚合器(类似于Prism),它允许使用者订阅一个返回Task
的处理程序,这样异步工作就可以在不使用async void
的情况下完成。我发现当你执行await aggregator.PublishAsync(new SomeEvent())
时,你最终会等待所有处理程序都执行完才能完成任务。如果你有一个花费2秒的昂贵的处理程序,那么发布将花费大约2秒。
现在允许使用者向聚合器订阅Action<TEvent>
或Func<TEvent, Task>
。订阅时,您还可以表示您是否希望在UI线程上执行该处理程序。我们希望通过返回Task
来给予等待所有处理程序或“激发/忽略”的选项。我们还希望确保如果您不等待发布,则“不要等2秒钟或更长的时间。所以这就是我们想出的:
public Task PublishAsync<T>(T @event) where T : IEvent
{
var subscriptions = _subscriptions
.Where(kvp => kvp.Value.EventName == typeof(T).Name)
.Select(kvp => kvp.Value)
.ToList();
// Task.Run is used so the work is done with an available thread in the thread pool
Task backgroundTask = Task.Run(async () =>
{
var backgroundTasks = subscriptions
.Where(s => !s.ExecuteOnUiThread)
.Select(s => s.Handler.Invoke(@event));
// _uiContext = SynchronizationContext.Current happens on construction (and is constructed in the main UI thread)
var uiThreadTasks = subscriptions
.Where(s => s.ExecuteOnUiThread)
.Select(s => { _uiContext.Post(obj => s.Handler.Invoke((T)obj), @event); return Task.CompletedTask; });
await Task.WhenAll(backgroundTasks.Concat(uiThreadTasks)).ConfigureAwait(false);
});
return backgroundTask;
}
我们有一个订阅事件的视图模型。处理程序更新绑定到Label的Text属性的属性。如果我们await
PublishAsync
调用,而该处理程序说不要使用UI线程,我将得到一个我所期望的“跨线程”异常。如果我触发/忘记并执行类似_ = _aggregator.PublishAsync(...);
的操作,则属性将被分配给,并且一切正常(即使我不在主UI线程上)。我很困惑。下面的截图怎么可能呢?执行第41行应该会抛出异常。x1c 0d1x
1条答案
按热度按时间wkftcu5l1#
因为我正在做一个fire/forget事件发布,所以所有的东西都执行得足够快,在调用处理程序之前视图模型就关闭了窗口。当调用处理程序的时候,与UI元素的连接(通过绑定)被切断了,设置那个属性是很好的,因为它没有继续进行,也没有与UI元素交互。
当我删除关闭窗口的代码并按照设计与事件聚合器交互时,无论是fire/forget还是async/await,我都会得到预期的跨线程异常。订阅指示在UI线程上执行它的处理程序也可以正常工作。