异步调用同步函数以实现UI响应

时间:2019-03-01 22:54:19

标签: c# winforms async-await

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

var l = new List<...>();
await Task.Run(() => l = GetReports().ToList());
UIControl.DataSource = l;

2 个答案:

答案 0 :(得分: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 :(得分:1)

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

  1. 启动一个新线程(术语,使用宽松)
  2. 创建延续
  3. 返回调用它的线程(在您的情况下为UI线程)
  4. 执行工作量
  5. 运行延续
    • 5a在您在
    • 上调用它的线程上await之后执行所有操作

尽管 Tasks 不是线程,但您会发现这将从 Thread Pool 中窃取线程来完成工作量,并且可以释放 UI线程,直到完成

您也可以使用较旧的样式Task.RunContinueWith做同样的事情。

还有另一种流派,如果您将TaskCreationOptionsLongRunning一起使用时,TaskFactory.StartNew会提示您要使用的默认TaskScheduler线程池之外创建一个线程。这样可以为线程池提供更多资源。

TaskFactory.StartNew Task 创建方法的祖父,它有自己的怪癖,您可能仅应在特别需要这样做时才使用它所以。我只会坚持你所拥有的。

最后一个注释,尽管将工作量包装在一个方法中并命名为async似乎是一个好主意,但这通常不是一个好主意。如果必须包装,则最好由调用者决定。因此,您再次做了正确的事。 Stephen Cleary 讨论了Fake AsyncAsync Wrappers以及您不需要这样做的原因

Task.Run Etiquette and Proper Usage