简体   繁体   中英

Async Web Service Requests from WPF

I have tried (and failed) to make asynchronous web service calls from a WPF application.

I implemented a BackgroundWorker in my code which should do the work, when you press the "Send" button on my GUI. It does what it's supposed to, some of the time , but ultimately it doesn't actually run asynchronous.

When you press the button in my GUI the following code fires:

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

sQuantity is just a box with a number in it. It will determine how many requests you sent to the web service at once.

progressBar is what you would expect: A progress bar.

worker.RunWorkerAsync() is where I call the DoWork method. It looks like this:

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

Ultimately, this is where I screw up of course. I want the application to send as many requests as the user specified. So if I wrote 10 in quantity, I would send 10 requests. The RequestQuote() method calls the following code:

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

How would I make the DoWork method actually send requests asynchronous?

The code as-is is asynchronous with respect to the UI thread; what you're asking about it concurrency . Any kind of complex I/O work is best done with async / await , so I'm going to throw out your background worker and just use straight async .

First, the button handler will handle its own enabling/disabling and executing the main download:

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

The main download will create a rate-limiting SemaphoreSlim (a common type used to throttle concurrent asynchronous operations), and wait for all the individual downloads to complete:

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

The individual downloads will each first rate-limit themselves, and then do the actual download:

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

For doing progress reports, I'd recommend using the types intended for that pattern ( IProgress<T> / Progress<T> ). First, you decide what data you want in your progress report; in this case, it could just be a string. Then, you create your progress handler:

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

(Note that progressBar.Maximum = double.Parse(sQuantity); in the original code was wrong; you should set it to the total number of downloads).

Then the IProgress<string> just gets passed down:

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

And when you have progress to report, you use that instance:

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

If you call RequestQuote with await keyword then in every call it waits for the response and you can't make another. so simply use your proxy's " completed event handler " for your async method and call RequestQuote method back to back and give your responses in the event handler.

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