【问题标题】:Async Web Service Requests from WPF来自 WPF 的异步 Web 服务请求
【发布时间】:2014-12-18 08:58:34
【问题描述】:

我已尝试(但失败)从 WPF 应用程序进行异步 Web 服务调用。

我在我的代码中实现了一个BackgroundWorker,当您按下我的 GUI 上的“发送”按钮时,它应该可以完成工作。 有时它会做它应该做的事情,但最终它实际上并没有异步运行。

当您在我的 GUI 中按下按钮时,会触发以下代码:

private void btnSend_Click(object sender, RoutedEventArgs e)
{
    sQuantity = boxQuantity.Text;
    progressBar.Maximum = double.Parse(sQuantity);
    worker.RunWorkerAsync();
}

sQuantity 只是一个盒子,里面有一个数字。它将确定您一次向 Web 服务发送多少请求。

progressBar 是您所期望的:进度条。

worker.RunWorkerAsync() 是我调用DoWork 方法的地方。它看起来像这样:

void worker_DoWork(object sender, DoWorkEventArgs e)
{
    EnableButton(false);
    List<LoanRequestNoCreditScoreDTO> dtoList = GetData();
    foreach (LoanRequestNoCreditScoreDTO dto in dtoList)
    {
        using (LoanBrokerWS.LoanBrokerWSClient client = new LoanBrokerWS.LoanBrokerWSClient())
        {
            try
            {
                Task<LoanQuoteDTO> lq = RequestQuote(dto, client);
                LoanQuoteDTO response = lq.Result;
                lq.Dispose();
                String responseMsg = response.SSN + "\n" + response.interestRate + "\n" + response.BankName + "\n------\n";
                AppendText(responseMsg);
                worker_ProgressChanged();
            }
            catch (Exception ex)
            {
                AppendText(ex.Message + "\n" + ex.InnerException.Message + "\n");
                worker_ProgressChanged();
            }
        }
    }
    EnableButton(true);
}

最终,这当然是我搞砸的地方。我希望应用程序发送与用户指定的请求一样多的请求。所以如果我写 10 个数量,我会发送 10 个请求。 RequestQuote() 方法调用如下代码:

private async Task<LoanQuoteDTO> RequestQuote(LoanRequestNoCreditScoreDTO dto, LoanBrokerWS.LoanBrokerWSClient client)
{
    LoanQuoteDTO response = await client.GetLoanQuoteAsync(dto.SSN, dto.LoanAmount, dto.LoanDuration);
    return response;
}

如何让DoWork 方法真正异步发送请求?

【问题讨论】:

  • 异步运行;您正在寻找的是并发执行。

标签: c# wpf web-services asynchronous


【解决方案1】:

代码原样相对于 UI 线程是异步的;你在问什么并发。任何复杂的 I/O 工作都最好使用 async/await 完成,所以我将扔掉你的后台工作人员,直接使用 async

首先,按钮处理程序将处理自己的启用/禁用和执行主下载:

private async void btnSend_Click(object sender, RoutedEventArgs e)
{
  var quantity = int.Parse(boxQuantity.Text);
  btnSend.Enabled = false;
  await DownloadAsync(quantity);
  btnSend.Enabled = true;
}

主下载将创建一个限速SemaphoreSlim(一种用于限制并发异步操作的常用类型),并等待所有单独的下载完成:

private async Task DownloadAsync(int quantity)
{
  var semaphore = new SemaphoreSlim(quantity);
  var tasks = GetData().Select(dto => DownloadAsync(dto, semaphore));
  await Task.WhenAll(tasks);
}

每个单独的下载都将首先限制自己的速率,然后进行实际下载:

private async Task DownloadAsync(LoanRequestNoCreditScoreDTO dto, SemaphoreSlim semaphore)
{
  await semaphore.WaitAsync();
  try
  {
    using (LoanBrokerWS.LoanBrokerWSClient client = new LoanBrokerWS.LoanBrokerWSClient())
    {
      var response = await RequestQuoteAsync(dto, client);
    }        
  }
  finally
  {
    semaphore.Release();
  }
}

对于制作进度报告,我建议使用适用于该模式的类型 (IProgress&lt;T&gt;/Progress&lt;T&gt;)。首先,您决定在进度报告中需要哪些数据;在这种情况下,它可能只是一个字符串。然后,您创建进度处理程序:

private async void btnSend_Click(object sender, RoutedEventArgs e)
{
  var quantity = int.Parse(boxQuantity.Text);
  var progress = new Progress<string>(update =>
  {
    AppendText(update);
    progressBar.Value = progressBar.Value + 1;
  });
  progressBar.Maximum = ...; // not "quantity"
  btnSend.Enabled = false;
  await DownloadAsync(quantity, progress);
  btnSend.Enabled = true;
}

(请注意,原代码中的progressBar.Maximum = double.Parse(sQuantity); 是错误的;您应该将其设置为总下载次数)。

然后IProgress&lt;string&gt; 就被传下来了:

private async Task DownloadAsync(int quantity, IProgress<string> progress)
{
  var semaphore = new SemaphoreSlim(quantity);
  var tasks = GetData().Select(dto => DownloadAsync(dto, semaphore, progress));
  await Task.WhenAll(tasks);
}

当您需要报告进度时,您可以使用该实例:

private async Task DownloadAsync(LoanRequestNoCreditScoreDTO dto, SemaphoreSlim semaphore, IProgress<string> progress)
{
  await semaphore.WaitAsync();
  try
  {
    using (LoanBrokerWS.LoanBrokerWSClient client = new LoanBrokerWS.LoanBrokerWSClient())
    {
      var response = await RequestQuoteAsync(dto, client);
      progress.Report(response.SSN + "\n" + response.interestRate + "\n" + response.BankName + "\n------\n");
    }
  }
  catch (Exception ex)
  {
    progress.Report(ex.Message + "\n" + ex.InnerException.Message + "\n");
  }
  finally
  {
    semaphore.Release();
  }
}

【讨论】:

  • 非常感谢您的回答(和课程):)
【解决方案2】:

如果您使用 await 关键字调用 RequestQuote,那么在每次调用中它都会等待响应,而您不能再做另一个。因此,只需将代理的“完成的事件处理程序”用于您的异步方法,然后背靠背调用 RequestQuote 方法并在事件处理程序中给出您的响应。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多