C# 异步和同步函数让我感到困惑

C# Async and Sync functions confuse me

我有一个应用程序,其中有一个按钮开始创建 XMLs。在 each XML 创建结束时,SendInvoice 函数发送它,接收响应和函数 ( ParseResponse) 解析响应并执行所需的数据库操作。

想法是当创建并发送所有 XML 时,应用程序必须关闭。 问题是我已经失去了对异步的控制,应用程序似乎在之前关闭,它实际上完成了所有的工作。此外 XML 在处理前一个之前发送。

ParseResponse 函数不是异步的。

这是 SendInvoice 函数。

你能推荐什么好的做法吗?

提前谢谢你。

public async void SendInvoice(string body)
    {
        Cursor.Current = Cursors.WaitCursor;

        var client = new HttpClient();

        var queryString = HttpUtility.ParseQueryString(string.Empty);

        var uri = "https://xxxx.xxx/SendInvoices?" + queryString; 

        HttpResponseMessage response;

        // Request body
        byte[] byteData = Encoding.UTF8.GetBytes(body);

        using (var content = new ByteArrayContent(byteData))
        {
            content.Headers.ContentType = new MediaTypeHeaderValue("application/xml");
            response = await client.PostAsync(uri, content);
            string responsebody = await response.Content.ReadAsStringAsync();

            ParseResponse(response.ToString());
            ParseResponse(responsebody);

        }
    }

剩下的代码

private void button1_Click(object sender, EventArgs e)
{
 For
  {
     ......
      SendInvoice(xml)

  }
  System.Windows.Forms.Application.Exit();
}

我很习惯多线程编程,我花了一些时间才理解异步编程,因为它真的和多线程无关。它是关于使用单个线程或少量线程做更多事情。

异步代码在 CPU 除了处理之外还要等待其他事情时是有益的。例如:等待网络响应、等待从磁盘读取数据、等待数据库服务器等单独进程。

它为您所在的线程提供了一种方法 运行 在您等待的同时做其他事情。 C# 使用 Task 执行此操作。任务是一些正在完成的工作,它可以是 运行ning 也可以是等待,等待时不需要附加线程。

所有异步函数都必须 return 一个有用的任务。所以你的函数应该是:

public async Task SendInvoice() {
...

编译器使用 async 关键字自动将您的函数包装在任务对象中,因此您无需担心很多细节。您只需在调用另一个异步函数时使用 await 即可。您可以自己做更多的工作来创建任务或 return 来自另一个异步函数的任务,甚至调用多个异步函数并一起等待它们。

如果您的异步方法 return 是一个值,请使用通用任务:例如 Task<String>

Task 是在任务完成之前从异步方法return编辑的。这就是允许线程被其他东西使用的原因,但它必须回到那个起点,这就是为什么异步编程你会听到“一路异步”。在它返回到有多个任务需要平衡的调用者之前,它并没有真正起到任何作用,这通常是您的应用程序或 Web 请求的入口点。

您可以使您的 C# Main 方法异步,但这通常无关紧要,除非您的进程确实同时执行多项操作。对于 Web 应用程序,可以只处理多个请求。对于独立应用程序,这意味着您可以查询多个 API,同时发出多个 Web 请求或数据库查询,并等待所有这些,只需使用一个线程即可。显然,这可以使事情变得更快(至少在本地,外部资源可能有更多工作要做)。

对于防止程序退出的简单方法,如果您有异步 main,只需 await 调用 SendInvoice。如果你的 main 不是异步的,你可以使用类似的东西:

SendInvoice().Wait()

SendInvoice().Result

使用Wait()Result 将锁定线程直到任务完成。它通常会使该线程专供任务使用,因此该线程不能用于任何其他任务。如果线程池中有更多线程,其他任务可能会继续 运行,但通常在单个任务上使用 Wait/Result 会破坏异步编程的意义,因此请记住这一点。

编辑 现在您已经发布了调用代码,看来您的调用处于循环中。这是利用异步调用并一次发送所有发票的好机会。

private async void button1_Click(object sender, EventArgs e)
{
  List<Task> tasks = new List<Task>();
  FOR
  {
     ......
     t = SendInvoice(xml).ConfigureAwait(false);
     tasks.Add(t)
  }

  await Task.WhenAll(tasks).ConfigureAwait(false);
  System.Windows.Forms.Application.Exit();

}

这将发送所有发票,然后 return 来自处理程序,然后在收到所有响应后退出。

由于您是从事件处理程序调用该方法,因此在这种情况下 async void 是可以接受的,将您的 Button Click 处理程序方法签名更改为使用 async,我还添加了一些 ConfigureAwait(false) 到异步方法调用 - best-practice-to-call-configureawait-for-all-server-side-code and :

private async void button1_Click(object sender, EventArgs e)
{
  //since you are using a for-loop, I'd suggest adding each Task
  //to a List and awaiting all Tasks to complete using .WhenAll()
  var tasks = new List<Task>();
  
  FOR
  {
     ......
      //await SendInvoice(xml).ConfigureAwait(false);
      tasks.Add(SendInvoice(xml));

  }
  await Task.WhenAll(tasks).ConfigureAwait(false);
  System.Windows.Forms.Application.Exit();

}

并将您的 SendInvoice 方法签名更改为 return a Task

public async Task SendInvoice(string body)
{
    Cursor.Current = Cursors.WaitCursor;

    var client = new HttpClient();

    var queryString = HttpUtility.ParseQueryString(string.Empty);

    var uri = "https://xxxx.xxx/SendInvoices?" + queryString; 

    HttpResponseMessage response;

    // Request body
    byte[] byteData = Encoding.UTF8.GetBytes(body);

    using (var content = new ByteArrayContent(byteData))
    {
        content.Headers.ContentType = new MediaTypeHeaderValue("application/xml");
        response = await client.PostAsync(uri, content).ConfigureAwait(false);
        string responsebody = await response.Content.ReadAsStringAsync().ConfigureAwait(false);

        ParseResponse(response.ToString());
        ParseResponse(responsebody);

   }
}