【问题标题】:Creating and starting a task on the UI thread在 UI 线程上创建和启动任务
【发布时间】:2015-01-28 14:38:43
【问题描述】:

当在工作线程上调用的方法需要在 UI 线程上运行代码并等待其完成后再执行其他操作时,可以这样完成:

    public int RunOnUi(Func<int> f)
    {
        int res = Application.Current.Dispatcher.Invoke(f);

        return res;
    }

但是如果我想用任务来做呢? RunOnUi 方法有没有办法创建在 UI 上启动的任务并将其返回,以便调用者(在工作线程上运行)可以等待它?符合以下签名的内容:public Task&lt;int&gt; StartOnUi(Func&lt;int&gt; f)?

一种方法如下:

public Task<int> RunOnUi(Func<int> f)
{
    var task = new Task<int>(f);
    task.Start(_scheduler);

    return task;
}

在这里,假设 _schduler 拥有 ui TaskScheduler。但我不太喜欢创建“冷”任务并使用 start 方法运行它们。这是“推荐”的方式还是有更优雅的方式?

【问题讨论】:

  • 这应该是你很少做的操作。如果你发现自己经常做这样的事情,这表明你的程序设计得不好。在 UI 环境中,您通常应该让大部分代码在 UI 线程中运行,并且只有非常本地化的部分没有 UI 交互被卸载到其他地方并且是await-ed。每当您觉得需要这样做时,通常表明您应该从操作中提取 UI 代码。
  • 你是对的。这是 WPF 应用程序作为 WCF 服务执行的一种特殊情况。每个服务调用都是在一个工作线程上接收的,该线程需要等待在 UI 上执行某些操作的任务(执行更改或读取其中的一些值)。
  • 例如WebView2.EnsureCoreWebView2Async 必须在 UI 线程中运行。

标签: c# wpf task-parallel-library task


【解决方案1】:

只需使用InvokeAsync 而不是Invoke,然后在函数返回的DispatcherOperation&lt;int&gt; 中返回Task&lt;int&gt;

//Coding conventions say async functions should end with the word Async.
public Task<int> RunOnUiAsync(Func<int> f)
{
    var dispatcherOperation = Application.Current.Dispatcher.InvokeAsync(f);
    return dispatcherOperation.Task;
}

如果您无法访问 .NET 4.5,情况会稍微复杂一些。您需要使用BeginInvokeTaskCompletionSource 来包装BeginInvoke 返回的DispaterOperation

    public Task<int> RunOnUi(Func<int> f)
    {
        var operation = Application.Current.Dispatcher.BeginInvoke(f);
        var tcs = new TaskCompletionSource<int>();
        operation.Aborted += (sender, args) => tcs.TrySetException(new SomeExecptionHere());
        operation.Completed += (sender, args) => tcs.TrySetResult((int)operation.Result);

        //The operation may have already finished and this check accounts for 
        //the race condition where neither of the events will ever be called
        //because the events where raised before you subscribed.
        var status = operation.Status;
        if (status == DispatcherOperationStatus.Completed)
        {
            tcs.TrySetResult((int)operation.Result);
        }
        else if (status == DispatcherOperationStatus.Aborted)
        {
            tcs.TrySetException(new SomeExecptionHere());
        }

        return tcs.Task;
    }

【讨论】:

  • 需要注意的是Dispatcher.InvokeAsync仅在.NET 4.5及以上版本中支持。
  • @KevinD。很公平,我也会使用 4.5 之前的解决方案进行更新。
  • @KevinD。刚刚注意到 OP 必须在 4.5 上,因为 Dispatcher.Invoke&lt;T&gt;(Func&lt;T&gt;) 也是 4.5 唯一的功能。
  • 我正在寻找 .net 4.5 的解决方案,谢谢大家
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-05-31
  • 2021-10-18
  • 1970-01-01
  • 2013-01-25
  • 1970-01-01
  • 2017-01-26
  • 2012-06-15
相关资源
最近更新 更多