异步调用同步函数以获得UI响应

问题描述 投票:1回答:2

我有一个同步方法GetReports(),其返回值将用于设置UI控件的数据源。运行可能需要一段时间。以下是异步调用它的惯用方法吗?

var l = new List<...>();
await Task.Run(() => l = GetReports().ToList());
UIControl.DataSource = l;
c# winforms async-await
2个回答
2
投票

您应该使用Microsoft的Reactive Framework(又名Rx) - NuGet System.Reactive.Windows.Forms并添加using System.Reactive.Linq; - 然后您可以这样做:

IDisposable subscription =
    Observable
        .Start(() => GetReports().ToList())
        .ObserveOn(UIControl)
        .Subscribe(list => UIControl.DataSource = list);

这很好地推送到一个新线程,然后在更新DataSource之前将其拉回。

如果您需要在完成之前取消,只需致电subscription.Dispose();

如果您对GetReports的调用是可取消的,那么您可以这样做:

IDisposable subscription =
    Observable
        .FromAsync(ct => GetReports(ct))
        .Select(x => x.ToList())
        .ObserveOn(UIControl)
        .Subscribe(list => UIControl.DataSource = list);

调用subscription.Dispose()现在也将取消该任务。


1
投票

如果您正在使用响应式UI并运行长时间运行的CPU工作负载(而不是可伸缩性),那么这很好并且可以实现您想要的。基本上它会

  1. 开始一个新线程(松散使用的术语)
  2. 创建一个延续
  3. 给回调用它的线程(在你的情况下是UI线程)
  4. 执行工作量
  5. 运行继续 5a在你调用它的线程上执行await之后的所有内容

虽然任务不是线程,但你会发现这会从线程池中窃取一个线程来完成你的工作量,它会释放UI线程直到它完成

你也可以用旧款Task.RunContinueWith做同样的事情。

还有另一种思想流派,如果你使用TaskFactory.StartNewTaskCreationOptions作为LongRunning,它将暗示你想在线程池外部创建一个线程的默认TaskScheduler。这样可以为线程池留出更多资源。

在说,TaskFactory.StartNew是Task创建方法的大爸爸,它有自己的怪癖,你可能只应该在你特别认为需要的时候才使用它。我会坚持你所拥有的。

最后一点,尽管将这个工作负载包装在一个方法中并将其称为async似乎是一个好主意,但它通常不是一个好主意;如果你必须包装,最好留给调用者来决定这些东西。所以你再次做正确的事情。 Stephen Cleary谈论Fake AsyncAsync Wrappers以及为什么你不需要这样做的原因

Task.Run Etiquette and Proper Usage

© www.soinside.com 2019 - 2024. All rights reserved.