使用 Task.Result 时防止 UI 冻结 [英] Prevent UI from freezing when using Task.Result

查看:40
本文介绍了使用 Task.Result 时防止 UI 冻结的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在调用 Task.Run(() => DoSomething()).Result 这会导致 UI 冻结,这是因为我使用了.Result".我需要 Result 因为我想返回值.

I am calling Task.Run(() => DoSomething()).Result which causes the UI to freeze and it happens because am using ".Result". I need Result because i want to return the value.

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

I don't want the Method StartSomething to be async because I don't want to await the method StartSomething. I want the await to happen at DoSomething().

所以基本上我需要一个异步方法被同步方法调用,而不会冻结 UI.另外,我想将异步方法中的值返回到按钮单击的顶层.

So basically I need a asynchronous method to be called by a synchronous method, without freezing the UI. Plus I want to return the value from the async method to the top level that is on Button Click.

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

Can this code be improved or is there any other solution?

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

推荐答案

Method StartSomething() 对我来说没有意义.它启动一个新的Task,然后只是同步等待这个任务的结果(.Result),这实际上是无用的——几乎[*] 与直接调用 DoSomething() 相同.另外 DoSomething() 已经是异步的,所以你不需要为它启动一个新的 Task.

Method StartSomething() doesn't make sense to me. It starts a new Task and then just synchronously waits for the result (.Result) of this task, which is effectively useless - it is nearly [*] the same as calling DoSomething() directly. Also DoSomething() is already asynchronous so you don't need to start a new Task for it.

看起来您根本不需要 StartSomething() 方法.如果你让 Button_Click 处理程序 async,你就可以直接 await DoSomething() :

It looks like you don't need StartSomething() method at all. If you make Button_Click handler async, you can then simply await DoSomething() directly:

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 all the way down 解决方案(如上所示)是 IMO 的首选方式,但如果您真的无法将调用代码更改为 async,我可以想两种方法从同步方法调用 async 方法而不阻塞 UI.首先是手动设置这样的延续任务:

While using async all the way down solution (as shown above) is IMO the preferred way, if you really can't change calling code to async, I can think of two ways to call async method from synchronous method without blocking UI. First is to manually set up a continuation tasks like this:

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 块等时,情况会变得更糟.

So you basicly split synchronous method (one split instead of each await) into several parts (continuation lambda methods) linked by .ContinueWith. This is similar to what await does under a hood. Problem is that unlike await (which produces nice and clean code), your code will be full of these continuation lambdas. And it will get much worse when you add exception handling blocks, using blocks, etc.

第二种方法是使用嵌套循环,例如.Stephen Toub 的 WaitWithNestedMessageLoop 扩展方法:

The second approach is using nested loops, eg. Stephen Toub's WaitWithNestedMessageLoop extension method:

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

嵌套循环是一种非常先进的技术(我实际上从未使用过),除非万不得已,否则我不建议使用它.

Nested loops are quite advanced technique (I actually never used it) and I don't recommend using it unless you have to.

[*]在异常处理、执行线程等方面存在差异,但这些与本题无关.

[*] There are differences in exception handling, executing thread, etc., but these are not relevant to this question.

这篇关于使用 Task.Result 时防止 UI 冻结的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆