wpf 在 UI 线程上创建和启动任务

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/28194943/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 12:53:24  来源:igfitidea点击:

Creating and starting a task on the UI thread

c#wpftask-parallel-librarytask

提问by Kobi Hari

When a method that gets called on a worker thread needs to run code on the UI thread and wait for it to complete before doing something else, it can be done like this:

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

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

        return res;
    }

But what if I wanted to do it with tasks? Is there a way for the RunOnUi method to create a task that is started on the UI and return it so that the caller (which runs on a worker thread) can wait for it? Something that will fit the following signature: public Task<int> StartOnUi(Func<int> f)?

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

One way to do it is as follows:

一种方法如下:

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

    return task;
}

Here, assume that _schdulerholds the ui TaskScheduler. But I am not too comfortable with creating "cold" tasks and using the start method to run them. Is that the "recommended" way or is there a more elegant way to do it?

在这里,假设_schduler持有 ui TaskScheduler。但是我不太习惯创建“冷”任务并使用 start 方法来运行它们。这是“推荐”的方式还是有更优雅的方式来做到这一点?

采纳答案by Scott Chamberlain

Just use InvokeAsyncinstead of Invokethen return the Task<int>inside the DispatcherOperation<int>the function returns.

只需使用InvokeAsync而不是Invoke然后返回函数返回的Task<int>内部DispatcherOperation<int>

//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;
}

If you do not have access to .NET 4.5 it is a little more complicated. You will need to use BeginInvokeand a TaskCompletionSourceto wrap the DispaterOperationthat BeginInvokereturns

如果您无法访问 .NET 4.5,则情况会稍微复杂一些。您将需要使用BeginInvokeTaskCompletionSource包装的DispaterOperationBeginInvoke回报

    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;
    }