【问题标题】:why the caller method and async method have the same thread id为什么调用者方法和异步方法具有相同的线程ID
【发布时间】:2016-01-07 00:14:42
【问题描述】:

请参考我下面的代码。

public MainViewModel()
    {
        LongRunningOperationCommand = new RelayCommand(ExecuteLongRunningOperationCommand);
    }

    private void ExecuteLongRunningOperationCommand()
    {
        Test();
    }

    private async Task Test()
    {
        Log += "Command begin: " + DateTime.Now + "\r\n";
        Log += "Command thread: " + Thread.CurrentThread.ManagedThreadId + "\r\n";
        var getStringAsync = GetStringAsync();
        Log += "Work in Command...\r\n";
        Log += "Work in Command which not related to the result of async method will complete: " + DateTime.Now + "\r\n";
        Log += "Work in Command which not related to the result of async method will complete, thread: " +
               Thread.CurrentThread.ManagedThreadId + "\r\n";
        string result = await getStringAsync;
        Log += "Command will complete: " + DateTime.Now + "\r\n";
        Log += "Command will complete, thread: " + Thread.CurrentThread.ManagedThreadId + "\r\n";
        Log += result + "\r\n";
    }

    private async Task<string> GetStringAsync()
    {
        Log += "Async method begin: " + DateTime.Now + "\r\n";
        Log += "Async method thread: " + Thread.CurrentThread.ManagedThreadId + "\r\n";
        Log += "Work in Async method... \r\n";
        await Task.Delay(10000);
        Log += "Async method will complete: " + DateTime.Now + "\r\n";
        Log += "Async method will complete, thread: " + Thread.CurrentThread.ManagedThreadId + "\r\n";
        return "GetStringAsync method completed!";
    }

结果如下

Command begin: 1/6/2016 11:58:37 PM
Command thread: 8
Async method begin: 1/6/2016 11:58:37 PM
Async method thread: 8
Work in Async method... 
Work in Command...
Work in Command which not related to the result of async method will complete: 1/6/2016 11:58:37 PM
Work in Command which not related to the result of async method will complete, thread: 8
Async method will complete: 1/6/2016 11:58:47 PM
Async method will complete, thread: 8
Command will complete: 1/6/2016 11:58:47 PM
Command will complete, thread: 8
GetStringAsync method completed!

GetStringAsync 方法中 await Task.Delay 之后的线程 id 应该与之前不同。为什么结果是一样的?在控制台应用程序中,线程 ID 不同,但在 WPF 应用程序中,它们是相同的。有人可以帮忙吗?

【问题讨论】:

  • += 不是线程安全的,如果多个线程同时尝试更新字符串(例如在您的控制台版本中),您可能会丢失日志消息。

标签: c# wpf multithreading asynchronous async-await


【解决方案1】:

async/await 的一大要点是,如果您有一个 SynchronizationContext,例如 WPF 的 DispatcherSynchronizationContext,则在该线程上开始的工作将在等待之后继续在该线程上,除非您告诉它不要这样做。

控制台应用程序没有 SynchronizationContext,因此它使用在线程池中调度线程的默认上下文,这就是您看到 WPF 和控制台应用程序的不同行为的原因。

要告诉 async/await 它不需要保持在相同的同步上下文中,您可以在等待时使用 .ConfigureAwait(false),然后它会在需要时使用默认线程池上下文来执行回调。

private async Task Test()
{
    Log += "Command begin: " + DateTime.Now + "\r\n";
    Log += "Command thread: " + Thread.CurrentThread.ManagedThreadId + "\r\n";
    var getStringAsync = GetStringAsync();
    Log += "Work in Command...\r\n";
    Log += "Work in Command which not related to the result of async method will complete: " + DateTime.Now + "\r\n";
    Log += "Work in Command which not related to the result of async method will complete, thread: " +
           Thread.CurrentThread.ManagedThreadId + "\r\n";
    string result = await getStringAsync.ConfigureAwait(false);
    Log += "Command will complete: " + DateTime.Now + "\r\n";
    Log += "Command will complete, thread: " + Thread.CurrentThread.ManagedThreadId + "\r\n";
    Log += result + "\r\n";
}

private async Task<string> GetStringAsync()
{
    Log += "Async method begin: " + DateTime.Now + "\r\n";
    Log += "Async method thread: " + Thread.CurrentThread.ManagedThreadId + "\r\n";
    Log += "Work in Async method... \r\n";
    await Task.Delay(10000).ConfigureAwait(false);
    Log += "Async method will complete: " + DateTime.Now + "\r\n";
    Log += "Async method will complete, thread: " + Thread.CurrentThread.ManagedThreadId + "\r\n";
    return "GetStringAsync method completed!";
}

注意,执行.ConfigureAwait(false) 并不能保证其余代码将在线程池中,如果任务处于Completed 状态,代码将同步执行并停留在最初称为await 的任何线程上.

有关详细信息,请参阅文章“It's All About the SynchronizationContext”。

【讨论】:

  • 也许值得注意的是,例如,如果 GetStringAsync() 有几个 awaits 是 ConfigureAwait(false)Test() 没有在其 awaits 中使用它,那么那些在 @s 中等待987654334@ 在可以运行的线程上是免费的,而Test() 中的线程将使其恢复原状。如果Test() 取决于上下文,而GetStringAsync() 没有,那么这将是两全其美。
  • @JonHanna 实际上,您可以通过在 getStringAsync 上不执行 .ConfigureAwait(false); 来看到这种行为。 Command will complete, thread: 将与 Command thread: 具有相同的线程 ID,但 Async method will complete, thread: 将具有不同的 ID(在 WPF 中)
  • 是的。我正在考虑每个人在实际代码中的位置,通常最好在不关心上下文的库和辅助方法中使用ConfigureAwait(false),但对于不“更接近”使用它至关重要到上下文对例如至关重要的 WPF(或其他)级别与 UI 相关的调用。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-01-10
  • 2020-12-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多