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

标签 c# .net multithreading asynchronous async-await

显然我还不理解async/await,下面的基本示例已经引起了一些头痛:

出于测试目的,我现在创建了一个代表我的 UI 的窗口,我想触发一个异步方法,该方法在窗口打开时在后台执行一些操作。我在我的窗口中添加了一个 Listview 来测试 UI 是否有响应。

当我执行下面的代码时,发生了两件我不明白的事情:

  1. 我的 ListView 显示了我在构造函数中定义的 CustomObjects。由于我无法在我的构造函数中使用 await 关键字,我期望 GetWebResponseAsync().ConfigureAwait(false) 的调用会阻塞我的 UI-Thread 并产生死锁(比如将调用替换为GetWebResponseAsync().Wait() 正在执行)。似乎 ConfigureAwait(false) 已经让我的方法在另一个线程上运行,即使我没有将它作为任务运行或等待它?
  2. UI 显示,但在 Async 方法运行时卡住。这实际上并不意外,但如果我考虑之前的观察结果,即在我执行异步方法时显然可以从代码访问 Listview,这会让我感到困惑。这是否意味着我的 UI 线程未被阻止,因此我应该能够与 UI 交互?

由于我被困在这里,我还想知道如何立即在构造函数中正确调用我的异步方法。如果我使用 await GetWebResponseAsync().ConfigureAwait(false); 它不会编译,因为我的构造函数没有 async 关键字。但是我现在知道我不应该使用 async void (即使我试图让我的构造函数成为 async void 方法,它也不会编译,因为 成员名称不能与其封闭类型相同)。

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 Answer 非常适合我。

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

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?

这是不正确的。该方法将同步运行,直到遇到第一个 await 关键字。在那之后,由于您没有在 GetWebResponseAsync 中使用 ConfigureAwait(false),它会将它的延续再次编码回 UI 线程,这可能就是您看到的原因你的 UI 卡住了。

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

同样,不。 async 方法不是在后台线程上运行,而是在 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) 并节省同步上下文编码(marshal)处理的开销。

关于c# - async/await - 我使用了错误的同步上下文吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28606556/

相关文章:

javascript - 在服务器端更新字段后防止触发 OnChange 函数

c# - CS0234 : The type or namespace name '<namespace2>' does not exist in the namespace '<namespace1>.' (are you missing an assembly reference? )

c++ - 共享内存系统性能上的消息传递接口(interface)

c++ - 中断正在执行 MKL 或其他第三方函数的线程

c# - 是否可以使用特定文件名创建 blob 触发的 azure 函数?

c# - 在 web api 中删除项目单元测试

c# - 将字符串格式化为日期时间

c# - Unity 中的 Mono 与 .NET

.net - 从 Windows 命令行运行 MSTests

java - 为什么 java.lang.Thread 在启动时不调用其显式 java.lang.Runnable 的 run() 方法?