2016-01-06 88 views
1

请参阅我的代码如下。为什么呼叫方法和异步方法具有相同的线程ID

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! 

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

+0

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

回答

5

异步/等待的一大要点是,如果你有一个SynchronizationContext,就像WPF的DispatcherSynchronizationContext一样,那个线程上的工作将在该线程后继续,除非你不告诉它。

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

要告诉异步/等待它不需要保持相同的同步上下文,当您等待时可以使用.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

查看artice“It's All About the SynchronizationContext”了解更多信息。

+1

也许值得注意的是,例如,如果'GetStringAsync()'有几个'Configure'等于'ConfigureAwait(false)'并且'Test()'没有在其'await'中使用它,那么这些等待' GetStringAsync()'在它们可以运行的线程方面是免费的,而'Test()'中的线程则会将它带回原来的线程。如果'Test()'依赖于上下文,但是'GetStringAsync()'没有,那么这将是两个世界中最好的。 –

+0

@JonHanna其实你可以通过在'getStringAsync'上不执行'.ConfigureAwait(false);'来看到这种行为。 '命令将完成,线程:'将与'命令线程:'具有相同的线程ID,但'异步方法将完成,线程:'将具有不同的ID(在WPF中) –

+0

是的。我正在考虑它是什么时候在真实代码中的位置,在库和辅助方法中使用'ConfigureAwait(false)'通常会更好,这些方法不关心上下文,但对于不使用它是至关重要的“更接近”WPF(或任何其他级别),其中上下文对于例如与UI相关的调用。 –