【问题标题】:Async / await on windows form applicationWindows窗体应用程序上的异步/等待
【发布时间】:2020-02-05 22:06:14
【问题描述】:

我正在尝试在我的应用程序上学习和实现 async / await 关键字。我正在使用 API 获取数据,然后将它们显示在我的表单上。当我尝试从控制台应用程序调用方法时没有问题。但是,如果我从 Form_Shown 事件中调用我的异步方法,也没有例外,但方法不起作用。

所以我在 Form_Shown 事件上调用 RefreshOrLoadDataToCache() 方法。

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

    RefreshOrLoadDataToCache(); // Async methods in it 

    // Some syncronous operations
}

在我的这个方法中创建了一个任务并等待它。

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

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

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

这是我的异步方法。

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

在这种方法中,我连接到 API 并获得结果。我认为我的问题是关于这种方法。因为当我从控制台应用程序调用此方法时,它会返回数据。但是从表单申请它等待很长时间但没有结果或异常。

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" };
    }
}

我认为我在 Windows 窗体上的异步调用中缺少一些东西。如何修复我的代码?

【问题讨论】:

    标签: c# asynchronous async-await


    【解决方案1】:

    你的代码有几个问题

    1. 您将方法标记为async,但您不等待内部操作。您目前这样做是因为RefreshOrLoadasync void。它实际上需要是async Task,其中底层返回的任务是正在进行的异步操作。然后,应该等待返回的Task

      private async void LogTimeReport_Shown(object sender, EventArgs e)
      {
          // Some syncronous operations
      
          await RefreshOrLoadDataToCache(); // Async methods in it 
      
          // Some syncronous operations
      }
      
    2. RefreshOrLoad 是一种异步方法。您使用Task.WhenAll,它用于异步等待多个任务,但您也没有使用await。然后,您致电.Resultwhich causes your code to effectively deadlock。所需要的只是等待从LoadTimeEntriesTemp返回的任务:

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

      我还要注意,您应该在异步方法中使用 *Async 后缀。

    修复这些问题后,您会看到代码的行为符合预期,一直是异步的。

    【讨论】:

      【解决方案2】:

      你的问题在这里:

      var taskForTimeEntries = LoadTimeEntriesTemp();
      Task.WhenAll(taskForTimeEntries);
      
      DataTable dtTimeEntriesTemp = taskForTimeEntries.Result;
      

      一开始,当你只有一个任务时,为什么要使用Task.WhenAll?这样你就会泄露Task.WhenAll 返回的任务,该任务将被完成,并表明你传递给Task.WhenAll 的所有任务都已完成。它会同步等待会导致死锁的任务。

      async/await 有一条规则,其中规定 以各种方式等待 所以正确的做法是:

      DataTable dtTimeEntriesTemp = await LoadTimeEntriesTemp();
      

      此外,如果您想执行与其结果相关的同步操作,您应该在事件处理程序中等待 RefreshOrLoadDataToCache();

      这是 Stephen Cleary Don't Block on Async Code 撰写的一篇很棒的文章,它更详细地描述了您的问题。

      【讨论】:

        【解决方案3】:

        方法 RefreshOrLoadDataToCache() 被标记为异步,但它不使用等待 Task.WhenAll()LogTimeReport_Shown() 不必是异步的。 :

        private async void RefreshOrLoadDataToCache()
        {
          if (IsNeededToCallAPI())
          {
            var taskForTimeEntries = LoadTimeEntriesTemp();
        
            DataTable dtTimeEntriesTemp = await taskForTimeEntries; // call await here
            DataTable dtEventsTemp = LoadEventsTemp();
        
            dtTimeEntriesTemp.Merge(dtEventsTemp);
          }
          else
            BindGridViews();
         }
        

        【讨论】:

          猜你喜欢
          • 2022-01-19
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-04-09
          • 1970-01-01
          • 2023-03-15
          • 2020-11-08
          相关资源
          最近更新 更多