简体   繁体   中英

Async / await on windows form application

I'm trying to learn and implement async / await keywords on my application. I'm using an API to get data then showing them on my forms. When I try to call methods from an console application there is no problem. But if I call my async methods from Form_Shown event also there no exception but methods not working.

So I'm calling my RefreshOrLoadDataToCache() method on Form_Shown event.

private async void LogTimeReport_Shown(object sender, EventArgs e)
{
    // Some syncronous operations

    RefreshOrLoadDataToCache(); // Async methods in it 

    // Some syncronous operations
}

In my this method created a task and wait for it.

private async void RefreshOrLoadDataToCache()
{
    if (IsNeededToCallAPI())
    {
        var taskForTimeEntries = LoadTimeEntriesTemp();
        Task.WhenAll(taskForTimeEntries);

        DataTable dtTimeEntriesTemp = taskForTimeEntries.Result;
        DataTable dtEventsTemp = LoadEventsTemp();

        dtTimeEntriesTemp.Merge(dtEventsTemp);
    }
    else
        BindGridViews();
}

This my async method.

 private async Task<DataTable> LoadTimeEntriesTemp()
 { 
     TimeEntryHandler timeHandler = new TimeEntryHandler();

     TimeEntryResponse response = await timeHandler.GetTimeEntries();

     DataTable dt = DatatableHelper.ToDataTable<TimeEntry>(response.TimeEntries);

     foreach (DataRow drow in dt.Rows)
     {
        // Some operations on DataTable
     }
     return dt;
 }

In this method I'm connecting to API and getting results. I think my problem is about this method. Because when I call this method from console application it returns data. But from form application it waits for a long time but there is no result or exception.

private async Task<TimeEntryResponse> GetTimeEntries()
{
    using (var client = new AuthorizedHttpClient(_client))
    {
        var data = await client.GetAsync<TimeEntryResponse>(parameters);

        if (data.StatusCode == HttpStatusCode.OK)
        {
            var response = (TimeEntryResponse)data.ContentObj;
            response.Pages = int.Parse(data.Headers.GetValues("X-Pages").First());
            response.Page = int.Parse(data.Headers.GetValues("X-Page").First());
            response.TotalRecords = int.Parse(data.Headers.GetValues("X-Records").First());
            return response;
        }
        return new TimeEntryResponse() { TimeEntries = null, STATUS = "ERROR" };
    }
}

I thought that there is something I'm missing about asyncronous calls on windows forms. How can I fix my code ?

You have a couple of problems with your code

  1. You mark a method as async , but you don't await on the operation inside. You currently do this because RefreshOrLoad is async void . It actually needs to be async Task , where the underlying returned task is the ongoing async operation. Then, the returned Task should be awaited on:

     private async void LogTimeReport_Shown(object sender, EventArgs e) { // Some syncronous operations await RefreshOrLoadDataToCache(); // Async methods in it // Some syncronous operations }
  2. RefreshOrLoad is an async method. You use Task.WhenAll , which is used for asynchronously waiting on multiple tasks, but you don't await on it either. Then, you call .Result , which causes your code to effectively deadlock . All that's needed is to await the task returning from LoadTimeEntriesTemp :

     private async Task RefreshOrLoadDataToCache() { if (IsNeededToCallAPI()) { DataTable dtTimeEntriesTemp = await LoadTimeEntriesTemp(); DataTable dtEventsTemp = LoadEventsTemp(); dtTimeEntriesTemp.Merge(dtEventsTemp); } else BindGridViews(); }

    I'd also note that you should use the *Async postfix with your async methods.

When fixing these, you'll see that your code behaves as expected, being asynchronous all the way down.

You problem here:

var taskForTimeEntries = LoadTimeEntriesTemp();
Task.WhenAll(taskForTimeEntries);

DataTable dtTimeEntriesTemp = taskForTimeEntries.Result;

At first, why do you use Task.WhenAll when you have just one task? That way you leak the task returned by Task.WhenAll which will be completed and indicate that all your tasks that are passed to Task.WhenAll are completed. It will wait for the task synchronously which will cause deadlock.

There is a rule for async/await which states await in all ways So right approach is:

DataTable dtTimeEntriesTemp = await LoadTimeEntriesTemp();

Also, you should await on RefreshOrLoadDataToCache(); in your event handler if you want to do synchronous operations related to its result.

Here is a great article by Stephen Cleary Don't Block on Async Code which describes your problem in more details.

Method RefreshOrLoadDataToCache() is marked as async yet it does not use await on Task.WhenAll() and LogTimeReport_Shown() does not have to be async. :

private async void RefreshOrLoadDataToCache()
{
  if (IsNeededToCallAPI())
  {
    var taskForTimeEntries = LoadTimeEntriesTemp();

    DataTable dtTimeEntriesTemp = await taskForTimeEntries; // call await here
    DataTable dtEventsTemp = LoadEventsTemp();

    dtTimeEntriesTemp.Merge(dtEventsTemp);
  }
  else
    BindGridViews();
 }

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