与后台线程交互时不会发生异常的情况

huangapple go评论70阅读模式
英文:

No exception when interacting with UI element in background thread

问题

以下是您要翻译的内容:

我们在代码库中使用了一个事件聚合器(类似于Prism)。它允许消费者订阅一个处理程序,该处理程序返回一个“Task”,以便可以执行异步工作而不使用“async void”。我发现当你执行await aggregator.PublishAsync(new SomeEvent())时,你需要等待所有处理程序执行完毕,然后任务才完成。如果有一个需要2秒钟的昂贵处理程序,那么发布操作将需要大约2秒钟。

现在,允许消费者订阅一个Action<TEvent>Func<TEvent, Task>到聚合器。您还可以在订阅时指定是否要在UI线程上执行处理程序。我们希望通过返回“Task”来提供等待所有处理程序或Fire/Forget的选项。我们还希望确保如果您不等待发布操作,您就不会等待大约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属性绑定的属性。如果我们等待PublishAsync调用,并且该处理程序指示不使用UI线程,那么我会得到一个“跨线程”异常,就像我所期望的那样。如果我执行Fire/Forget并像这样做_ = _aggregator.PublishAsync(...);,则属性将被分配,并且一切正常(即使我不在主UI线程上)。我很茫然。下面的屏幕截图是如何可能的?执行第41行本应引发异常。

英文:

We have an event aggregator (similar to Prism) that is used in our codebase. It allows consumers to subscribe a handler that returns a Task so asynchronous work can be done without using async void. I found that when you do await aggregator.PublishAsync(new SomeEvent()) you end up waiting for all handlers to execute before the task is done. If you have an expensive handler that takes 2 seconds then that publish will take ~2 seconds.

It is now allowed for consumers to subscribe an Action<TEvent> or Func<TEvent, Task> to the aggregator. You can also, when subscribing, say whether or not you want that handler to be executed on the UI thread or not. We wanted to give the option to wait for all handlers or Fire/Forget by returning the Task. We also wanted to make sure if you don't await the publish that you aren't waiting the ~2 seconds or however long. So this is what we came up with:

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;
}

We have a view model that is subscribing to an event. The handler updates a property that is bound to a Label's Text property. If we await the PublishAsync call and that handler says not to use the UI thread I'll get a "cross thread" exception like I expect. If I fire/forget and do something like _ = _aggregator.PublishAsync(...); the property is assigned to and everything works (even though I'm NOT on the main UI thread). I'm at a loss. How is the below screenshot possible? Executing line 41 should've thrown an exception.
与后台线程交互时不会发生异常的情况

答案1

得分: 1

因为我正在进行事件的"发布并忘记",所以在视图模型关闭窗口之前,一切都执行得足够快。当处理程序被调用时,通过绑定与UI元素的连接已断开,因此设置该属性是可以的,因为它不会继续与UI元素交互。

当我移除了关闭窗口并按设计与事件聚合器交互的代码时,无论是"发布并忘记"还是"async/await",都如预期地出现了跨线程异常。订阅处理程序,表示要在UI线程上执行,也是有效的。

英文:

Because I was doing a fire/forget publish of the event everything executed fast enough for the window to be closed by the view model before the handler was called. By the time the handler was called the connection with the UI elements (through binding) was severed and setting that property was fine since it wasn't going ahead and interacting with UI elements.

When I removed the code that closed the window and interacted with the event aggregator as designed I was getting the cross-thread exceptions as expected regardless of fire/forget or async/await. Subscribing the handler that says to execute it on the UI thread was also working.

huangapple
  • 本文由 发表于 2023年2月24日 00:55:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/75547936.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定