我有一个同步方法GetReports()
,其返回值将用于设置UI控件的数据源。运行可能需要一段时间。以下是异步调用它的惯用方法吗?
var l = new List<...>();
await Task.Run(() => l = GetReports().ToList());
UIControl.DataSource = l;
您应该使用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()
现在也将取消该任务。
如果您正在使用响应式UI并运行长时间运行的CPU工作负载(而不是可伸缩性),那么这很好并且可以实现您想要的。基本上它会
await
之后的所有内容虽然任务不是线程,但你会发现这会从线程池中窃取一个线程来完成你的工作量,它会释放UI线程直到它完成
你也可以用旧款Task.Run
和ContinueWith
做同样的事情。
还有另一种思想流派,如果你使用TaskFactory.StartNew
和TaskCreationOptions
作为LongRunning
,它将暗示你想在线程池外部创建一个线程的默认TaskScheduler
。这样可以为线程池留出更多资源。
在说,TaskFactory.StartNew
是Task创建方法的大爸爸,它有自己的怪癖,你可能只应该在你特别认为需要的时候才使用它。我会坚持你所拥有的。
最后一点,尽管将这个工作负载包装在一个方法中并将其称为async
似乎是一个好主意,但它通常不是一个好主意;如果你必须包装,最好留给调用者来决定这些东西。所以你再次做正确的事情。 Stephen Cleary谈论Fake Async
和Async Wrappers
以及为什么你不需要这样做的原因