使用Task.Result时防止UI冻结

时间:2018-11-16 05:37:52

标签: c# .net task

我正在调用Task.Run(()=> DoSomething())。Result,它导致UI冻结,并且由于使用“ .Result”而发生。我需要Result,因为我想返回值。

我不希望StartSomething方法异步,因为我不想等待StartSomething方法。我希望等待发生在DoSomething()。

所以基本上,我需要一个异步方法由一个同步方法调用,而不会冻结UI。另外,我想将异步方法的值返回到Button Click的顶级。

可以改进此代码还是有其他解决方案?

private TaskCompletionSource<bool> TaskCompletion = null;
private void Button_Click(object sender, RoutedEventArgs e)
    {
        bool k = StartSomething();
    }

    private bool StartSomething()
    {
        return Task.Run(() => DoSomething()).Result;
    }

    private async Task<bool> DoSomething()
    {
        TaskCompletion = new TaskCompletionSource<bool>();
        await Task.WhenAny(TaskCompletion.Task, Task.Delay(3000));
        MessageBox.Show("DoSomething");
        return true;
    }

1 个答案:

答案 0 :(得分:2)

方法StartSomething()对我来说没有意义。它开始一个新的Task,然后同步地等待此任务的结果(.Result),实际上是无用的-它几乎是 [*] < / strong>与直接调用DoSomething()相同。此外,DoSomething()已经是异步的,因此您无需为其启动新的Task

看来您根本不需要StartSomething()方法。如果您使Button_Click处理程序async,则只需直接await DoSomething()即可:

private TaskCompletionSource<bool> TaskCompletion = null;

private async void Button_Click(object sender, RoutedEventArgs e)
{
    bool k = await DoSomething();
}

private async Task<bool> DoSomething()
{
    TaskCompletion = new TaskCompletionSource<bool>();
    await Task.WhenAny(TaskCompletion.Task, Task.Delay(3000));
    MessageBox.Show("DoSomething");
    return true;
}


编辑:

虽然一直使用 async 解决方案(如上所示)是IMO的首选方法,但是如果您确实无法将调用代码更改为async,我可以想到两个同步方法中调用async方法而不阻塞UI的方法。首先是手动设置像这样的延续任务:

private void Button_Click(object sender, RoutedEventArgs e)
{
    DoSomething().ContinueWith((task) =>
        {
            bool k = task.Result;

            // use the result
        },

        // TaskScheduler argument is needed only if the continuation task
        // must run on the UI thread (eg. because it access UI elements).
        // Otherwise this argument can be omitted.
        TaskScheduler.FromCurrentSynchronizationContext());

    // Method can exit before DoSomething().Result becomes
    // available, which keep UI responsive
}

因此,您基本上将同步方法(由一个{而不是每个await拆分)拆分为几个由.ContinueWith链接的部分(连续lambda方法)。这类似于await在幕后所做的事情。问题在于,与await(产生漂亮而干净的代码)不同,您的代码将充满这些连续lambda。当您添加异常处理块,using块等时,情况将变得更加糟糕。

第二种方法是使用嵌套循环。 Stephen Toub的WaitWithNestedMessageLoop扩展方法:

static T WaitWithNestedMessageLoop<T>(this Task<T> task)
{
    var nested = new DispatcherFrame();
    task.ContinueWith(_ => nested.Continue = false, TaskScheduler.Default);
    Dispatcher.PushFrame(nested);
    return task.Result;
}

嵌套循环是一种非常先进的技术(我实际上从未使用过),除非有必要,否则我不建议您使用它。


[*] 在异常处理,执行线程等方面存在差异,但与该问题无关。