简体   繁体   中英

C# Async and Sync functions confuse me

I have an app in which a button starts creating XMLs. In the end of each XML creation, the SendInvoice function sends it, receives the response and a function ( ParseResponse ) parses the responses and does the database operations needed.

The idea is that when all the XMLs are created and sent, the application must close. The problem is that I have lost control with async and the application seems to close before it actually finishes all the jobs. Also XMLs are sent before the previous have been processed.

The ParseResponse function is not asynchronous.

Here is the SendInvoice function.

Can you suggest any good practise?

Thank you in advance.

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

        }
    }

The rest of the code

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

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

Since you are calling the method from an Event Handler, this is a case where async void is acceptable, change your Button Click handler method signature to use async , I also added some ConfigureAwait(false) to async method calls - best-practice-to-call-configureawait-for-all-server-side-code and why-is-writing-configureawaitfalse-on-every-line-with-await-always-recommended :

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();

}

AND change your SendInvoice method signature to 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);

   }
}

I was very used to multithreaded programming and it took me some time to understand asynchronous programming, because it really has nothing to do with multithreading. It is about doing more with a single thread, or a small number of threads.

asynchronous code is beneficial when the CPU would otherwise be waiting for something besides processing. Examples are: waiting for a network response, waiting for data to be read from disk, waiting on a separate process such as a database server.

It provides a way for the thread you are running to do other things while you wait. C# does this using Task . A task is some work that is being done, and it can be running or it can be waiting, and when waiting it doesn't need a thread attached.

All asynchronous functions must return a Task to be useful. So your function should be:

public async Task SendInvoice() {
...

The async keyword is used by the compiler to automatically wrap your function in a task object so you don't need to worry about a lot of the details. You just use await when calling another async function. You could do more work yourself to create tasks or return a task from another async function, or even call multiple async functions and await all of them together.

If your async method returns a value, use the generic Task: Task<String> , for example.

The Task is returned from an async method before the task completes. That is what allows the thread to be used by something else, but it has to get back to that starting place, which is why asynchonous programming you'll hear "async all the way up". It doesn't really do any good until it gets back to a caller that has multiple tasks to balance, which is usually the entry point of your application or the web request.

You can make your C# Main method async, but it mostly won't matter unless your process is really doing multiple things at the same time. For a web application, that can just be handling multiple requests. For a standalone app, it means you can query multiple APIs, make multiple web requests or db queries at the same time, and await them all, just using a single thread. Obviously, that can make things faster (at least locally, the external resources may have more work to do).

For a simple way to keep your program from exiting, if you have an asynchronous main , just await the call to SendInvoice . If your main is not async, you can use something like:

SendInvoice().Wait()

or

SendInvoice().Result

Using Wait() or Result will lock the thread until the task completes. It typically will make that thread exclusively available to the task so the thread cannot be used for any other tasks. If there are more threads in the threadpool, other tasks may continue to run, but typically using Wait/Result on a single Task defeats the point of asynchronous programming, so keep that in mind.

EDIT Now that you have posted your calling code, it appears your call is in a loop. This is a good opportunity to take advantage of async calls and send ALL the invoices at once.

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();

}

That will send ALL the invoices, then return from the handler, and then exit once all the responses have been received.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM