我可能会做这一切都错了,但我被困住了。我有一个 GUI 应用程序,它产生一个单独的线程,从服务器下载一堆数据。当这个下载线程完成时,我希望它向主线程发送一个信号,以便它知道它现在可以显示下载的数据。
我尝试调用 Invoke(从我的主窗体)调用委托来完成显示工作,但这会阻塞我的下载器线程,直到它完成。我有点想只做一个没有 EndInvoke 的 BeginInvoke,但我知道这样做不合适。
我可能会做这一切都错了,但我被困住了。我有一个 GUI 应用程序,它产生一个单独的线程,从服务器下载一堆数据。当这个下载线程完成时,我希望它向主线程发送一个信号,以便它知道它现在可以显示下载的数据。
我尝试调用 Invoke(从我的主窗体)调用委托来完成显示工作,但这会阻塞我的下载器线程,直到它完成。我有点想只做一个没有 EndInvoke 的 BeginInvoke,但我知道这样做不合适。
有几个选项。
我个人最喜欢的是使用 TPL。在您的 UI 线程上,您可以制作一个TaskFactory
,如下所示:
// Given:
// TaskFactory uiFactory;
uiFactory = new TaskFactory(TaskScheduler.FromCurrentSynchronizationContext());
然后,在您的后台任务中,您可以创建一个Task
来更新您的 UI:
var task = uiFactory.StartNew( () => UpdateUserInterface(data));
这将正确地编组到 UI 线程,类似于BeginInvoke
调用。如果需要阻塞,可以调用task.Wait()
(或者task.Result
如果 Update 方法返回值)。
有几种选择:
“TPL 除了默认的调度器之外还有其他调度器,还允许您创建自定义调度器。TPL 提供的调度器之一是基于当前同步上下文的,它可用于确保我的任务在 UI 上执行线。” (来源文章):
var ui = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.ContinueWhenAll(tasks.ToArray(),
result =>
{
var time = watch.ElapsedMilliseconds;
label1.Content += time.ToString();
}, CancellationToken.None, TaskContinuationOptions.None, ui);
在下载场景的情况下,.ContinueWith()
继续是合适的。