问题
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