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.