C# UI线程上的任务继续

C# UI线程上的任务继续,c#,.net,wpf,multithreading,task,C#,.net,Wpf,Multithreading,Task,是否有一种“标准”方法来指定任务延续应该在创建初始任务的线程上运行 目前我有下面的代码-它正在工作,但跟踪调度器并创建第二个操作似乎是不必要的开销 dispatcher = Dispatcher.CurrentDispatcher; Task task = Task.Factory.StartNew(() => { DoLongRunningWork(); }); Task UITask= task.ContinueWith(() => { dispatcher.I

是否有一种“标准”方法来指定任务延续应该在创建初始任务的线程上运行

目前我有下面的代码-它正在工作,但跟踪调度器并创建第二个操作似乎是不必要的开销

dispatcher = Dispatcher.CurrentDispatcher;
Task task = Task.Factory.StartNew(() =>
{
    DoLongRunningWork();
});

Task UITask= task.ContinueWith(() =>
{
    dispatcher.Invoke(new Action(() =>
    {
        this.TextBlock1.Text = "Complete"; 
    }
});

使用任务调度器调用continuation。FromCurrentSynchronizationContext():


这仅适用于当前执行上下文位于UI线程上的情况。

如果需要将返回值发送到UI,则可以使用以下通用版本:

在我的例子中,这是从MVVM视图模型调用的

var updateManifest = Task<ShippingManifest>.Run(() =>
    {
        Thread.Sleep(5000);  // prove it's really working!

        // GenerateManifest calls service and returns 'ShippingManifest' object 
        return GenerateManifest();  
    })

    .ContinueWith(manifest =>
    {
        // MVVM property
        this.ShippingManifest = manifest.Result;

        // or if you are not using MVVM...
        // txtShippingManifest.Text = manifest.Result.ToString();    

        System.Diagnostics.Debug.WriteLine("UI manifest updated - " + DateTime.Now);

    }, TaskScheduler.FromCurrentSynchronizationContext());
var updateManifest=Task.Run(()=>
{
Thread.Sleep(5000);//证明它真的有效!
//GenerateManifest调用服务并返回“ShippingManifest”对象
返回GenerateManifest();
})
.ContinueWith(清单=>
{
//MVVM属性
this.ShippingManifest=manifest.Result;
//或者如果您没有使用MVVM。。。
//txtShippingManifest.Text=manifest.Result.ToString();
System.Diagnostics.Debug.WriteLine(“UI清单更新-”+DateTime.Now);
},TaskScheduler.FromCurrentSynchronizationContext());

我只想添加这个版本,因为这是一个非常有用的线程,我认为这是一个非常简单的实现。我已在各种类型的多线程应用程序中多次使用此选项:

 Task.Factory.StartNew(() =>
      {
        DoLongRunningWork();
        Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
              { txt.Text = "Complete"; }));
      });

使用async,您只需执行以下操作:

await Task.Run(() => do some stuff);
// continue doing stuff on the same context as before.
// while it is the default it is nice to be explicit about it with:
await Task.Run(() => do some stuff).ConfigureAwait(true);
然而:

await Task.Run(() => do some stuff).ConfigureAwait(false);
// continue doing stuff on the same thread as the task finished on.

我是通过谷歌来这里的,因为我正在寻找一种很好的方法来完成任务后在ui线程上的操作。运行call-使用以下代码,您可以使用
wait
再次返回ui线程

我希望这对某人有帮助

public static class UI
{
    public static DispatcherAwaiter Thread => new DispatcherAwaiter();
}

public struct DispatcherAwaiter : INotifyCompletion
{
    public bool IsCompleted => Application.Current.Dispatcher.CheckAccess();

    public void OnCompleted(Action continuation) => Application.Current.Dispatcher.Invoke(continuation);

    public void GetResult() { }

    public DispatcherAwaiter GetAwaiter()
    {
        return this;
    }
}
用法:

。。。在后台线程上执行的代码。。。
等待UI线程;
... 将在应用程序调度程序(ui线程)中运行的代码。。。

仅当当前执行上下文位于UI线程上时才有效。如果将此代码放在另一个任务中,则会得到.NET 4.5中的InvalidOperationException(请参阅小节),Johan Larsson的答案应作为UI线程上任务继续的标准方式。只需写:等待任务。运行(DoLongRunningWork);this.TextBlock1.Text=“完成”;另见:Thx拯救我的生命。我花了几个小时来弄清楚如何在wait/ContinueWith中调用主线程。对于其他人来说,这是一种有效的方法。@MarcelW-
await
是一种很好的模式-但只有在
async
上下文(例如声明的
async
方法)中时才是如此。如果不是的话,仍然有必要这样做。不要投反对票,因为在某些情况下这是一个可行的解决方案;然而,公认的答案要好得多。它与技术无关(
TaskScheduler
是BCL的一部分,
Dispatcher
不是),可以用来组成复杂的任务链,因为不必担心任何火灾和忘记异步操作(例如
BeginInvoke
),因为如果使用WinForms的WPF,一些SO线程一致声明dispatcher是正确的方法:可以异步(使用BeginInvoke)或同步(invoke)调用GUI更新,但通常使用异步,因为不希望仅为了GUI更新而阻塞后台线程。FromCurrentSynchronizationContext是否以与dispatcher相同的方式将延续任务放入主线程消息队列?是的,但OP肯定在询问WPF(并将其标记为WPF),并且不希望保留对任何dispatcher的引用(我也假设任何同步上下文——您只能从主线程获得它,并且您必须在某个地方存储对它的引用)。这就是我喜欢我发布的解决方案的原因:有一个内置的线程安全静态引用不需要这些。我认为这在WPF上下文中非常有用。我只是想强调我的最后一点:开发人员不仅要存储同步上下文,而且他/她必须知道这只能从主线程获得;t他的问题一直是几十个SO问题中混乱的原因:人们总是试图从工作者线程中获取这些信息。如果他们的代码本身被移动到工作者线程中,它就会因为这个问题而失败。因此,由于WPF的盛行,这一点在这个流行的问题中应该得到明确的澄清……然而,Dean的观察结果是,如果代码可能不在主线程上,则[接受的答案]需要跟踪同步上下文,这一点很重要,避免这种情况是这个答案的一个好处。我猜GenerateManifest之前的=是一个输入错误。在您的示例中,您可以使用
Control.Invoke(Action)
,即
TextBlock1.Invoke
而不是
dispatcher.Invoke
谢谢@ColonelPanic,但我使用的是WPF(已标记),而不是winforms。
false
版本下的注释让我感到困惑。我认为
false
意味着它可能会在另一个线程上继续。@ToolmakerSteve取决于您考虑的线程。Task.Run使用的工作线程,还是调用线程?记住,“任务完成的同一个线程”意味着工作线程(避免在线程之间“切换”)。此外,ConfigureAwait(true)不保证控制返回到同一线程,只返回到同一上下文(尽管区别可能不明显)。@MaxBarraclough-谢谢,我误读了哪个“同一线程”这对我来说是很清楚的。避免在线程之间切换,通过使用任何正在运行的线程[执行“做一些事情”任务]来最大化性能。问题并没有指定在
async
方法中(使用
wait
是必要的).当
wait
不可用时,答案是什么
public static class UI
{
    public static DispatcherAwaiter Thread => new DispatcherAwaiter();
}

public struct DispatcherAwaiter : INotifyCompletion
{
    public bool IsCompleted => Application.Current.Dispatcher.CheckAccess();

    public void OnCompleted(Action continuation) => Application.Current.Dispatcher.Invoke(continuation);

    public void GetResult() { }

    public DispatcherAwaiter GetAwaiter()
    {
        return this;
    }
}