在一种方法中调用同步和异步方法(api / UI)的正确方法是什么

时间:2017-07-24 12:15:38

标签: c# asynchronous

在下面的示例中,我在Sync方法(UI)中调用Async方法。 在Async方法中,我调用另一个Async方法(例如api调用),但我也调用其他同步方法(例如更新组合框)。现在我使用Invoke((MethodInvoker ...用于每个同步方法调用。这是正确的方法,它可以更好吗?不,我也必须考虑使用Invoke((MethodInvoker ...在异步调用同步方法时)。

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

2 个答案:

答案 0 :(得分:3)

让我们分解这里发生的事情。

当你的control_SelectionValueChanged处理程序触发时,我假设我们正在UI线程上运行。然后你:

  • 通过SomeMethodAsync在线程池线程上启动Task.Run。这不会阻止UI线程。
  • 一旦线程池线程开始执行SomeMethodAsync,您就要求运行时通过调用Control.Invoke返回封送到UI线程。当SomeMethodA正在UI线程上执行时,您也同时阻塞了线程池线程。
  • 然后取消阻塞线程池线程并要求它执行其他async方法。整个操作将保持在UI主题之外(除非SomeOtherMethodAsync内有一些时髦的东西,即另一个Control.Invoke来电)
  • await之后返回一个线程池线程 - 这可能是与await之前相同的线程池线程,或者是另一个线程池线程 - 这是最高为TaskScheduler
  • 如果variabletrue,则在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();
}

以上在行为方面类似(尽管不完全相同),但阅读起来不是更容易吗?

答案 1 :(得分:2)

我建议不要混合它们。但是,您在事件处理程序中的事实允许规则中的例外,您可以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();
}