【问题标题】:Async-await seems to use the UI threadAsync-await 似乎使用了 UI 线程
【发布时间】:2013-11-18 16:39:57
【问题描述】:

在视图模型中我使用工厂:

private async Task<BaseData> InitializeAsync()
{
    await InstancesAsync();
    await ProjectsAsync();
    await AdminAsync();
    return this;
}
public static async Task<BaseData> CreateAsync()
{
    var ret = new BaseData();
    return await ret.InitializeAsync();
}

等待的方法相当简单,例如

 var instances = await TaskEx.Run(new Func<List<string>>(() => Agent.GetInstances()));

在 wpf 视图中我想在构造函数中设置 DataContext:

Loaded += delegate
{
    Dispatcher.Invoke(new Action(async () => { DataContext = await BasisGegevens.CreateAsync(); }));
};

虽然它可以工作,但我感觉很不舒服,因为 UI 线程无处不在,在等待完成时的回调之后也是如此。我错过了什么?

另外我不明白如何为DataContext 使用工厂模式,因为如果没有上面的Invoke,我会收到另一个线程拥有该对象的错误。

编辑:使用克利里先生的想法,我得到:

Loaded += async (object sender, RoutedEventArgs e) =>
          { DataContext = await BaseData.CreateAsync(); };
public static Task<BaseData> CreateAsync()
{
    var ret = new BaseData();
    return ret.InitializeAsync();
}
private async Task<BaseData> InitializeAsync()
{
    // UI thread here
    await InstancesAsync().ConfigureAwait(false);
    // thread 'a' here
    await ProjectsAsync().ConfigureAwait(false);
    // thread 'a' sometimes 'b' here
    await AdminAsync().ConfigureAwait(false);
    // thread 'a' or 'b' here
    return this;
}

这很好用,但我无法理解 ConfigureAwait(false) 的工作原理。
在方法InstancesAsync() 里面我有等待的任务:
var instances = await TaskEx.Run(new Func&lt;List&lt;string&gt;&gt;(() =&gt; Agent.GetInstances()));
等待回复后,我返回 UI 线程 - 我没想到会发生这种情况!
请注意,ProjectsAsync()AdminAsync() 的行为相同,尽管它们从工作(或后台)线程开始!
我认为ConfigureAwait(true) 具有在调用线程(在我的情况下为 UI 线程)中返回的效果。我对此进行了测试,确实如此。
为什么我也看到ConfigureAwait(false):因为嵌套等待,请参阅 cmets。

【问题讨论】:

  • 您展示的代码中Loaded 事件的来源是什么?如果这是一个常规的FrameworkElement.Loaded 事件,它应该在 UI 线程上触发。您是否在单独的线程上创建视图?这可能是因为您在非 UI 池线程上调用 Agent.GetInstances()
  • Loaded 事件是一个常规的 FrameworkElement.Loaded 事件。我打算在 UI 线程上创建视图,这显然是错误的。 Agent.GetInstances() 在等待的任务中运行,我希望它在非 UI 池线程上,但事实并非如此。
  • TaskEx.Run 确实使用单独的池线程来运行您的 Func 委托,这就是执行 Agent.GetInstances() 的地方。我建议您遵循斯蒂芬的回答,并确保 WPF UI 元素和 ViewModel 对象都是在同一个 UI 线程上创建的。添加一些日志记录 (System.Threading.Thread.CurrentThread.ManagedThreadId) 以查看您在哪个线程上以及在哪里。
  • ConfigureAwait(false) 只影响特定的await,它对“内部”的awaits 没有影响。
  • @svick:当然,谢谢,我也查过了。

标签: wpf async-await


【解决方案1】:

我发现将 ViewModel 视为具有 UI 线程关联性最有用。将其视为逻辑 UI,即使它不是 实际 UI。因此,ViewModel 类的所有属性和可观察集合更新都应该在 UI 线程上完成。

在您的async 方法中,如果您不需要返回UI 线程,那么您可以使用ConfigureAwait(false) 来避免在UI 线程上恢复。例如,如果你的各种初始化方法是独立的,你可以这样做:

private async Task<BaseData> InitializeAsync()
{
  // Start all methods on the UI thread.
  var instancesTask = InstancesAsync();
  var projectsTask = ProjectsAsync();
  var adminTask = AdminAsync();

  // Await for them all to complete, and resume this method on a background thread.
  await Task.WhenAll(instancesTask, projectsTask, adminTask).ConfigureAwait(false);

  return this;
}

另外,任何时候你有return await,再看看你是否可以完全避免async/await

public static Task<BaseData> CreateAsync()
{
  var ret = new BaseData();
  return ret.InitializeAsync();
}

最后,你应该强烈避免Dispatcher。您的Loaded 事件可以简化为:

Loaded += async ()
{
  DataContext = await BasisGegevens.CreateAsync();
};

【讨论】:

猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-04-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-18
  • 1970-01-01
  • 2020-11-18
相关资源
最近更新 更多