在一种方法中调用同步和异步方法(api/UI)的正确方法是什么
问题描述:
在下面的示例中,我在Sync方法(UI)中调用Async方法。 在异步方法中,我调用另一个异步方法(例如API调用),但我也调用其他同步方法(例如更新组合框)。现在我使用Invoke((MethodInvoker ...)来调用每个同步方法,这是否是正确的方法,它可以更好吗?不,我还必须考虑使用Invoke((MethodInvoker ...)在同步方法中调用同步方法。在一种方法中调用同步和异步方法(api/UI)的正确方法是什么
private void control_SelectionValueChanged(Object sender, EventArgs e)
{
Task task = Task.Run(async() => await SomeMethodAsync());
}
private async Task SomeMethodAsync()
{
Invoke((MethodInvoker)(() => SomeMethodA))
bool variable = await SomeOtherMethodAsync()
if (variable) Invoke((MethodInvoker)(() => SomeMethodB))
Invoke((MethodInvoker)(() => SomeMethodC))
}
答
让我们打破这里发生了什么
当您control_SelectionValueChanged
处理程序便会启动,我认为我们在UI线程上运行,则然后:。
- 揭开序幕
SomeMethodAsync
上线池线程通过Task.Run
。这不会阻止UI线程。 - 一旦线程池线程开始执行
SomeMethodAsync
您正在要求运行时通过调用Control.Invoke
将您的返回回传给UI线程。虽然SomeMethodA
正在UI线程上执行,但同时也会阻塞线程池线程。 - 然后,您解除对线程池线程的阻塞并要求它执行一些其他的
async
方法。整个操作将保持关闭UI
线程(除非里面有SomeOtherMethodAsync
东西时髦,即另一个Control.Invoke
呼叫) - 后
await
返回到一个线程池线程 - 这可能是同一个线程池中的线程作为前await
,或不同的 - 这是由TaskScheduler
。 - 如果
variable
是true
,则在UI线程上执行SomeMethodB
(同时再次阻塞线程池线程)。 - 最后,您在UI线程上执行
SomeMethodC
(同时最后一次阻塞线程池线程)。
正如你所看到的,大部分时间SomeMethodAsync
正在执行(与花等待SomeOtherMethodAsync
的时间以外,与Control.Invoke
调用之间短暂的时间),你仍然在使用UI线程,但你也阻塞你的线程池线程。所以你现在正在占用两条线程,其中大多数只有其中一个线程正在做有用的工作 - 另一线程只是坐在那里等待。
除了非常可怕的阅读,这是非常低效。
考虑以下改写:
private async void control_SelectionValueChanged(Object sender, EventArgs e)
{
try
{
await SomeMethodAsync();
}
catch (Exception ex)
{
// We're an async void, so don't forget to handle exceptions.
MessageBox.Show(ex.Message);
}
}
private async Task SomeMethodAsync()
{
// We're on the UI thread, and we will stay on the UI
// thread *at least* until we hit the `await` keyword.
SomeMethodA();
// We're still on the UI thread, but if `SomeOtherMethodAsync`
// is a genuinely asynchronous method, we will go asynchronous
// as soon as `SomeOtherMethodAsync` hits the its `await` on a
// `Task` that does not transition to `Completed` state immediately.
bool variable = await SomeOtherMethodAsync();
// If you need stronger guarantees that `SomeOtherMethodAsync`
// will stay off the UI thread, you can wrap it in Task.Run, so
// that its synchronous portions (if any) run on a thread pool
// thread (as opposed to the UI thread).
// bool variable = await Task.Run(() => SomeOtherMethodAsync());
// We're back on the UI thread for the remainder of this method.
if (variable) SomeMethodB();
// Still on the UI thread.
SomeMethodC();
}
以上是在行为方面相似(虽然不是完全等同),不过,是不是更容易阅读?
答
我会建议不要混合它们。然而,事实是,你是在一个事件处理程序允许在规则的一个例外,你可以有async void
private async void control_SelectionValueChanged(Object sender, EventArgs e) {
SomeMethodA(); //On UI
bool variable = await SomeOtherMethodAsync(); // Non blocking
//Back on UI
if (variable) SomeMethodB();
SomeMethodC();
}
难道'任务task = SomeMethodAsync()'你非异步方法更有意义。MethodInvoker代码的逻辑是什么? – Magnus
为什么它会更有意义? Invoke调用的方法是属于UI线程的同步方法。 SomeOtherMethodAsync是api调用(异步)。 –
这条线对我来说毫无意义。您创建一个任务只是为了等待另一个任务。为什么不直接返回第一个任务(SomeMethodAsync的结果)。 – Magnus