【问题标题】:What is the right way of calling sync and async methods (api / UI) in one method在一种方法中调用同步和异步方法(api / UI)的正确方法是什么
【发布时间】:2026-02-03 10:15:02
【问题描述】:

在下面的示例中,我在 Sync 方法 (UI) 中调用了 Async 方法。 在异步方法中,我调用了另一个异步方法(例如 api 调用),但我也调用了其他同步方法(例如更新组合框)。现在我对每个同步方法调用使用 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))
}

【问题讨论】:

  • 在你的非异步方法中Task task = SomeMethodAsync() 不会更有意义。 MethodInvoker 代码的逻辑是什么?
  • 为什么更有意义? Invoke 调用的方法是属于 UI 线程的同步方法。 SomeOtherMethodAsync 是一个 api 调用(异步)。
  • 那行对我来说毫无意义。您创建一个任务只是为了等待另一个任务。为什么不直接返回第一个任务(SomeMethodAsync 的结果)。

标签: c# asynchronous


【解决方案1】:

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

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

  • 通过Task.Run 在线程池线程上启动SomeMethodAsync。这不会阻塞 UI 线程。
  • 一旦线程池线程开始执行SomeMethodAsync,您就要求运行时通过调用Control.Invoke 将您返回到UI 线程。当SomeMethodA 在 UI 线程上执行时,您同时也阻塞了您的线程池线程。
  • 然后您解除阻塞线程池线程并要求它执行一些其他async 方法。整个操作将远离UI 线程(除非SomeOtherMethodAsync 内部有一些时髦的东西,即另一个Control.Invoke 调用)
  • await 之后,您将返回到一个 线程池线程——这可能是与await 之前相同的线程池线程,也可以是不同的——这取决于@ 987654333@.
  • 如果 variabletrue,则在 UI 线程上执行 SomeMethodB(再次阻塞线程池线程)。
  • 最后,在 UI 线程上执行SomeMethodC(同时最后一次阻塞线程池线程)。

如您所见,大部分时间SomeMethodAsync 正在执行(除了等待SomeOtherMethodAsyncControl.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();
}

上面的内容在行为方面是相似的(尽管不完全等同),但不是更容易阅读吗?

【讨论】:

    【解决方案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();
    }
    

    【讨论】: