强制 async-await IProgress<T>.Report() 同步

ʞᴉɯ*_*ʞᴉɯ 3 .net c# async-await

我正在使用基于任务的异步模式 (TAP)来完成一些长任务,IProgress<T>用于向主 UI 报告进度。在Progress.Report似乎只有当它被其他的await任务之前工作。例如,如果我在内联 for 循环中使用,则报告消息仅在任务结束时发布:

public async Task<bool> DoSomething(IProgress<string> progress)
{
    progress.Report("Start");  // works
    await SomeTask();

    progress.Report("Message 1"); // works ONLY at end

    for ()
    {
        progress.Report("Message x"); // works ONLY at end
        // do some tasks inline
    }

    return true;
}
Run Code Online (Sandbox Code Playgroud)

有什么方法可以强制同步发布报告消息吗?谢谢。

Yuv*_*kov 6

Progress.Report 似乎只有在它之前有另一个等待任务时才起作用。

那讲得通。一旦您调用该方法,就会Progress<T>捕获SynchronizationContext并发布到它Report。如果你的异步方法不是真正的异步并且大部分 CPU 工作是在 UI 线程上完成的,那么你不会释放消息循环来处理更多事件,因此你只会在结束时看到它更新方法调用。

这是如何Progress<T>.Report实现的:

protected virtual void OnReport(T value)
{
        // If there's no handler, don't bother going through the [....] context.
        // Inside the callback, we'll need to check again, in case 
        // an event handler is removed between now and then.
        Action<T> handler = m_handler;
        EventHandler<T> changedEvent = ProgressChanged;
        if (handler != null || changedEvent != null)
        {
            // Post the processing to the [....] context.
            // (If T is a value type, it will get boxed here.)
            m_synchronizationContext.Post(m_invokeHandlers, value);
        }
}
Run Code Online (Sandbox Code Playgroud)

为了保持响应,您可以将for循环卸载到线程池线程:

public async Task<bool> DoSomethingAsync(IProgress<string> progress)
{
    progress.Report("Start");  // works
    await SomeTask();
    progress.Report("Message 1");

    await Task.Run(() =>
    {
        progress.Report("Message x");
        // Do more CPU bound work
    }
    return true;
}
Run Code Online (Sandbox Code Playgroud)

  • @kim 您需要释放 UI 线程以允许它更新。如果您实际上没有任何异步工作要做,您可以使用“Task.Run”在线程池线程上执行其他 CPU 绑定工作,这样您就不会最终冻结 UI (2认同)