异步/计谋 - 我使用了错误的同步范围内? [英] async/await - am I using the wrong synchronisation context?

查看:179
本文介绍了异步/计谋 - 我使用了错误的同步范围内?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

显然,我不明白异步/计谋又和以下基本的例子已经导致一些头痛:

有关测试的目的我创建了一个窗口,它重新presents我的UI现在,我想大火确实在后台的一些工作的窗口打开时,一个异步方法。我添加了一个列表视图我的窗前来测试用户界面响应。

当我执行以下code,两件事情发生,我不明白:

  1. 在我的的ListView 显示了 CustomObjects ,我在构造函数中定义。因为我无法用等待的关键字在我的构造,我期待了的通话GetWebResponseAsync()。ConfigureAwait(假)会阻止我的UI线程,并产生死锁(如更换与通话GetWebResponseAsync()。等待()正在做)。这似乎是 ConfigureAwait(假)已经让我在另一个线程上运行的方法,即使我没有把它作为任务或等待呢?
  2. 在用户界面显示出来,但异步方法运行时冻结。这其实并不意外,但混淆了我,如果我认为previous观察到ListView控件从code显然访问,而我执行我的异步方法。难道这不是意味着,我的UI线程没有被阻塞,因此我应该能够与UI交互?

既然我来到这里卡住了,我也想知道我可以适当地叫我的异步方法在构造函数的时候了。如果我使用等待GetWebResponseAsync()ConfigureAwait(假); 它并不能编译,因为我的构造不具备异步关键字。但是我现在知道我不应该使用异步无效(即使我尽量让我的构造函数异步无效方法,它不会编译,因为成员名称不能与它们的封闭类型)。

 公共部分类titleWindow正在:窗口
{
    公共titleWindow正在()
    {
        的InitializeComponent();

        //我不能用这个,因为构造函数是不是异步。
        //任务tempTask =等待GetWebResponseAsync()ConfigureAwait(假)。

        。GetWebResponseAsync()ConfigureAwait(假);

        //这应该在理论考试,如果我的UI线程被阻塞?
        名单< CustomObject>项目=新的名单,其中,CustomObject>();
        items.Add(新CustomObject(){标题=CustomTitle,年份= 2100});
        items.Add(新CustomObject(){标题=CustomTitle2,年份= 2015});
        lvTitles.ItemsSource =项目;

    }

    公共异步任务GetWebResponseAsync(){
        WebRequest的请求= WebRequest.Create(http://www.google.com);
        request.Credentials = CredentialCache.DefaultCredentials;
        WebResponse的响应=等待request.GetResponseAsync();

        //输出用于测试目的。
        流式传输的数据流= response.GetResponseStream();
        StreamReader的读者=新的StreamReader(数据流);
        字符串responseFromServer =等待reader.ReadToEndAsync();
        Console.WriteLine(responseFromServer);
        返回;
    }
}
 

更新:

尤瓦Itzchakovs回答工作完全正常的我。

另外这个图案(从尤瓦Itzchakovs答案链路采取)或两者的组合看起来的方式去在一个更复杂的情况。这提供了一个非常舒适的可能性,以确保以后的构造函数中的异步code已经通过等待我的初始化属性完成。

 公共部分类titleWindow正在:窗口,IAsyncInitialization
{
    公共任务初始化{获得;私人集;}

    公共titleWindow正在()
    {
        的InitializeComponent();
        初始化= GetWebResponseAsync();
    }

    公共异步任务GetWebResponseAsync(){
          //不变
    }
 

解决方案
  

好像ConfigureAwait(假)已经使我的方法运行   另一个线程,即使我没有把它作为任务或等待呢?

这是不正确。该方法将同步运行,直到打到第计谋关键字。在此之后,因为你没有使用 ConfigureAwait(假) GetWebResponseAsync ,将元帅它的延续再次返回到在UI线程,这可能是为什么你看到你的用户界面会被卡住。

  

难道这不是意味着,我的UI线程没有被阻塞,因此我应该   能够与UI交互?

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

从调用构造函数异步方法不自然地工作,因为构造函数不是异步(斯蒂芬·克利里有一个很好的的博客文章有关)。

您的什么的做的是使用被附加到后,加载窗口,如加载事件,该事件触发一个事件,你可以执行异步方法内正常:

  this.Loaded + = OnWindowLoaded;
 

然后就可以正常等待

 专用异步无效OnWindowLoaded(对象发件人,RoutedEventArgs E)
{
    等待GetWebResponseAsync()ConfigureAwait(假)。
}
 

请注意,如果没有必要的内部 GetWebResponseAsync 同步情况下,你也可以使用 ConfigureAwait(假)并保存自己的同步上下文编组的开销。

Apparently i did not understand async/await yet and the following basic example already causes some headache:

For test purposes i create a Window which represents my UI for now and i want to fire an asynchronous method which does some work in the background when the window opens. I added a Listview to my window to test if the UI is responding.

When i execute the following code, two things happen that i do not understand:

  1. My ListView shows the CustomObjects that i define in the constructor. Since i could not use the await keyword in my constructor, I was expecting that the call of GetWebResponseAsync().ConfigureAwait(false) would block my UI-Thread and produce a deadlock (like replacing the call with GetWebResponseAsync().Wait() is doing). It seems like ConfigureAwait(false) already makes my method run on another thread even though i did not run it as a task or await it?
  2. The UI shows up, but freezes while the Async method is running. This is actually not unexpected but confuses me if i consider the previous observation that the Listview is apparently accessible from the code while i perform my async method. Doesn't that mean that my UI-Thread is NOT blocked and thus I should be able to interact with the UI?

Since i got stuck here, i would also like to know how i can properly call my async method in the constructor right away. If i use await GetWebResponseAsync().ConfigureAwait(false); it doesn't compile since my constructor does not have the async keyword. However i know by now that i should not use async void (and even if i try to make my constructor an async void method, it doesn't compile because 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;
    }
}

Update:

Yuval Itzchakovs Answer works perfectly fine for me.

Also this pattern (taken from the link in Yuval Itzchakovs answer) or a combination of both seems like the way to go in a more complex scenario. This provides a very comfortable possibility to ensure later on that the async code from the constructor is already completed by awaiting my Initialization Property.

public partial class TitleWindow : Window, IAsyncInitialization
{
    public Task Initialization{get; private set;}

    public TitleWindow()
    {
        InitializeComponent();
        Initialization = GetWebResponseAsync();
    }

    public async Task GetWebResponseAsync(){
          //unchanged
    }

解决方案

It seems like ConfigureAwait(false) already makes my method run on another thread even though i did not run it as a task or await it?

That's incorrect. The method will run synchronously until hitting the first await keyword. After that, since you're not using ConfigureAwait(false) in GetWebResponseAsync, it will marshal it's continuation again back onto the UI thread, which is probably why you're seeing you UI get stuck.

Doesn't that mean that my UI-Thread is NOT blocked and thus I should be able to interact with the UI?

Again, no. The async method isn't running on a background thread, it's consumed on the UI thread. When the method runs its synchronous part (such as Stream dataStream = response.GetResponseStream();) it's still executing on the UI thread.

Calling async method from a constructor doesn't naturally work because constructors aren't async (Stephan Cleary has a good blog post about that).

What you can do is use is attach to an event which fires after the window is loaded, such as the Loaded event, which you can execute async methods inside properly:

this.Loaded += OnWindowLoaded;

And then you can properly await:

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

Note that if there's no need for the synchronization context inside GetWebResponseAsync, you can also use ConfigureAwait(false) and save yourself the overhead of the sync context marshaling.

这篇关于异步/计谋 - 我使用了错误的同步范围内?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆