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

C# 在一个方法中调用同步和异步方法(api/UI)的正确方法是什么,c#,asynchronous,C#,Asynchronous,在下面的示例中,我在同步方法UI中调用异步方法。 在异步方法中,我调用了另一个异步方法api调用,但我也调用了其他同步方法,例如update combobox。现在我使用InvokeMethodInvoker。。。对于每个同步方法调用。这是正确的方法吗?能做得更好吗?不,我还必须考虑使用InvokeMethodInvoker。。。在异步方法中调用同步方法时 private void control_SelectionValueChanged(Object sender, EventArgs e)

在下面的示例中,我在同步方法UI中调用异步方法。 在异步方法中,我调用了另一个异步方法api调用,但我也调用了其他同步方法,例如update combobox。现在我使用InvokeMethodInvoker。。。对于每个同步方法调用。这是正确的方法吗?能做得更好吗?不,我还必须考虑使用InvokeMethodInvoker。。。在异步方法中调用同步方法时

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

我们来分析一下这里发生的事情

当您的控件\u SelectionValueChanged处理程序启动时,我假设我们正在UI线程上运行。然后你:

通过Task.Run在线程池线程上启动SomeMethodAsync。这不会阻止UI线程。 一旦线程池线程开始执行SomeMethodAsync,您就要求运行时通过调用Control.Invoke将您封送回UI线程。当SomeMethodA在UI线程上执行时,您同时也在阻塞线程池线程。 然后解除对线程池线程的阻止,并要求它执行其他异步方法。除非SomeOtherMethodAsync中有什么奇怪的东西,例如另一个控件。Invoke调用,否则整个操作都将远离UI线程 等待之后,返回线程池线程-这可能是等待之前的相同线程池线程,也可能是不同的线程池线程-这取决于TaskScheduler。 如果variable为true,则在UI线程上执行SomeMethodB,同时再次阻塞线程池线程。 最后,在UI线程上执行SomeMethodC,同时最后一次阻塞线程池线程。 如您所见,大部分时间SomeMethodAsync都在执行,但等待SomeOtherMethodAsync的时间除外,以及控件之间的短暂间隔。调用您仍在使用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();
}

上面的内容很相似,尽管在行为方面并不完全相同,但阅读起来不是更容易吗?

让我们来分析一下这里发生的事情

当您的控件\u SelectionValueChanged处理程序启动时,我假设我们正在UI线程上运行。然后你:

通过Task.Run在线程池线程上启动SomeMethodAsync。这不会阻止UI线程。 一旦线程池线程开始执行SomeMethodAsync,您就要求运行时通过调用Control.Invoke将您封送回UI线程。当SomeMethodA在UI线程上执行时,您同时也在阻塞线程池线程。 然后解除对线程池线程的阻止,并要求它执行其他异步方法。除非SomeOtherMethodAsync中有什么奇怪的东西,例如另一个控件。Invoke调用,否则整个操作都将远离UI线程 等待之后,返回线程池线程-这可能是等待之前的相同线程池线程,也可能是不同的线程池线程-这取决于TaskScheduler。 如果variable为true,则在UI线程上执行SomeMethodB,同时再次阻塞线程池线程。 最后,在UI线程上执行SomeMethodC,同时最后一次阻塞线程池线程。 如您所见,大部分时间SomeMethodAsync都在执行,但等待SomeOtherMethodAsync的时间除外,以及控件之间的短暂间隔。调用您仍在使用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


我建议不要把它们混在一起。但是,您处于事件处理程序中这一事实允许在规则中出现异常,您可以在其中使用async void


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