winforms 在后台线程中与UI元素交互时无异常

k4emjkb1  于 2023-02-24  发布在  其他
关注(0)|答案(1)|浏览(114)

我们有一个事件聚合器(类似于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属性的属性。如果我们awaitPublishAsync调用,而该处理程序说不要使用UI线程,我将得到一个我所期望的“跨线程”异常。如果我触发/忘记并执行类似_ = _aggregator.PublishAsync(...);的操作,则属性将被分配给,并且一切正常(即使我不在主UI线程上)。我很困惑。下面的截图怎么可能呢?执行第41行应该会抛出异常。x1c 0d1x

wkftcu5l

wkftcu5l1#

因为我正在做一个fire/forget事件发布,所以所有的东西都执行得足够快,在调用处理程序之前视图模型就关闭了窗口。当调用处理程序的时候,与UI元素的连接(通过绑定)被切断了,设置那个属性是很好的,因为它没有继续进行,也没有与UI元素交互。
当我删除关闭窗口的代码并按照设计与事件聚合器交互时,无论是fire/forget还是async/await,我都会得到预期的跨线程异常。订阅指示在UI线程上执行它的处理程序也可以正常工作。

相关问题