async / await - 我使用了错误的同步上下文吗?

时间:2015-02-19 12:33:58

标签: c# .net multithreading asynchronous async-await

显然我还不理解async/await,以下基本示例已经引起了一些麻烦:

出于测试目的,我创建了一个代表我的UI的窗口,我想要触发一个异步方法,该方法在窗口打开时在后台运行。我在窗口中添加了Listview来测试用户界面是否正在响应。

当我执行以下代码时,发生了两件我不理解的事情:

  1. 我的ListView显示我在构造函数中定义的CustomObjects。由于我无法在构造函数中使用await关键字,因此我期望GetWebResponseAsync().ConfigureAwait(false)的调用会阻塞我的UI-Thread并产生死锁(就像用GetWebResponseAsync().Wait()替换调用一样)。似乎ConfigureAwait(false)已经使我的方法在另一个线程上运行,即使我没有将它作为任务运行或等待它?
  2. UI显示,但在Async方法运行时冻结。这实际上并不意外,但如果我考虑先前的观察结果,当我执行我的异步方法时,Listview显然可以从代码中访问,这让我感到困惑。这是不是意味着我的UI-Thread没有被阻止,因此我应该能够与UI进行交互?
  3. 由于我被困在这里,我还想知道如何在构造函数中正确调用我的异步方法。如果我使用await GetWebResponseAsync().ConfigureAwait(false);它不会编译,因为我的构造函数没有async关键字。但是我现在知道我不应该使用async void(即使我尝试使我的构造函数成为async void方法,它也不会编译,因为member names can not be the same as their enclosing type)。

    public partial class TitleWindow : Window
    {
        public TitleWindow()
        {
            InitializeComponent();
    
            // I can not use this because the constructor is not async.
            //Task tempTask = await GetWebResponseAsync().ConfigureAwait(false);
    
            GetWebResponseAsync().ConfigureAwait(false);
    
            //This should in theory test if my UI-Thread is blocked?!
            List<CustomObject> items = new List<CustomObject>();
            items.Add(new CustomObject() { Title = "CustomTitle", Year = 2100});
            items.Add(new CustomObject() { Title = "CustomTitle2", Year = 2015});
            lvTitles.ItemsSource = items;
    
        }
    
        public async Task GetWebResponseAsync(){
            WebRequest request = WebRequest.Create("http://www.google.com");
            request.Credentials = CredentialCache.DefaultCredentials;
            WebResponse response = await request.GetResponseAsync();
    
            //Output for test purposes.
            Stream dataStream = response.GetResponseStream();
            StreamReader reader = new StreamReader(dataStream);
            string responseFromServer = await reader.ReadToEndAsync();
            Console.WriteLine(responseFromServer);
            return;
        }
    }
    

    更新

    Yuval Itzchakovs答案对我来说非常好。

    此模式(取自Yuval Itzchakovs答案中的链接)或两者的组合似乎是更复杂场景的方式。这提供了一种非常舒适的可能性,以便稍后通过等待我的初始化属性来完成构造函数的异步代码。

    public partial class TitleWindow : Window, IAsyncInitialization
    {
        public Task Initialization{get; private set;}
    
        public TitleWindow()
        {
            InitializeComponent();
            Initialization = GetWebResponseAsync();
        }
    
        public async Task GetWebResponseAsync(){
              //unchanged
        }
    

1 个答案:

答案 0 :(得分:6)

  

似乎ConfigureAwait(false)已经使我的方法运行   另一个线程,即使我没有将它作为任务运行或等待它?

那是不对的。该方法将同步运行,直到达到第一个await关键字。之后,由于您未在ConfigureAwait(false)中使用GetWebResponseAsync,因此会将其再次封送回UI线程,这可能就是您为什么会看到您的原因用户界面卡住了。

  

这并不意味着我的UI-Thread没有被阻止,因此我应该这样做   能够与UI交互吗?

再一次,没有。异步方法不在后台线程上运行,它在UI线程上消耗。当该方法运行其同步部分(例如Stream dataStream = response.GetResponseStream();)时,它仍然在UI线程上执行。

从构造函数调用异步方法并不自然起作用,因为构造函数不是异步(Stephan Cleary对此有一个很好的blog post)。

所做的是使用附加到窗口加载后触发的事件,例如Loaded事件,您可以在其中正确执行异步方法:

this.Loaded += OnWindowLoaded;

然后你可以正确await

private async void OnWindowLoaded(object sender, RoutedEventArgs e)
{
    await GetWebResponseAsync().ConfigureAwait(false);
}

请注意,如果GetWebResponseAsync内不需要同步上下文,您还可以使用ConfigureAwait(false)并节省同步上下文封送的开销。