Prevent UI from freezing when using Task.Result

做~自己de王妃 提交于 2021-01-29 17:02:12

问题


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.

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().

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

回答1:


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.

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


Edit:

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
}

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.

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.



来源:https://stackoverflow.com/questions/53332038/prevent-ui-from-freezing-when-using-task-result

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!