I have the following Async method:
private async void ProcessSearch()
{
// get catalogs on first search
if (_invoiceTypes == null && _invoiceAccounts == null)
{
var confWcf = new Data.ConfigurationWCF();
_invoiceTypes = await confWcf.GetInvoiceTypesAsync(MainForm.State.Entity);
_invoiceAccounts = await confWcf.GetInvoiceAccountsAsync(MainForm.State.Entity);
confWcf.Dispose();
}
var seekWcf = new DataSeekWCF();
_ds = await seekWcf.SearchInvoiceAdminAsync(new Guid(cboEmployer.Value.ToString()), new Guid(cboGroup.Value.ToString()), txtSearchInvoiceNumber.Text, chkSearchLike.Checked, txtSearchFolio.Text, Convert.ToInt32(txtYear.Value));
seekWcf.Dispose();
if (_ds != null)
{
SetupInvoiceGrid();
}
}
I don't want to execute SetupInvoiceGrid until _invoiceTypes, _invoiceAccounts and _ds have finished.
Any clue? Am I doing it right? Should I be using Task instead of await?
I have come up with this code that seems is working and looks fine to me but don't really know if its correct:
private void btnSearch_Click(object sender, EventArgs e)
{
lock (lockObj)
{
if (_isBusy)
return;
else
_isBusy = true;
}
ShowPleaseWait(Translate("Searching data. Please wait..."));
if (_invoiceTypes == null && _invoiceAccounts == null)
{
var t = GetCatalogs();
t.ContinueWith(t2 =>
{
if (t.IsCompleted) ProcessSearch();
});
}
else
{
ProcessSearch();
}
}
private async Task GetCatalogs()
{
// get catalogs on first search
Data.ConfigurationWCF confWcf = new Data.ConfigurationWCF();
var task1 = confWcf.GetInvoiceTypesAsync(1);
var task2 = confWcf.GetInvoiceAccountsAsync(1);
confWcf.Dispose();
await Task.WhenAll(task1, task2);
_invoiceTypes = task1.Result;
_invoiceAccounts = task2.Result;
if (_invoiceTypes != null)
{
cboInvoiceType.DataSource = _invoiceTypes.Tables["invoice_types"];
cboInvoiceType.DisplayMember = "description";
cboInvoiceType.ValueMember = "code";
}
}
private async void ProcessSearch()
{
var seekWcf = new Data.SeekWCF();
_ds = await seekWcf.SearchInvoiceAdminAsync(new Guid(cboEmployer.Value.ToString()), new Guid(cboGroup.Value.ToString()), txtSearchInvoiceNumber.Text, chkSearchLike.Checked, txtSearchFolio.Text, Convert.ToInt32(txtYear.Value));
seekWcf.Dispose();
if (_ds != null)
{
SetupInvoiceGrid();
}
HidePleaseWait();
}
I answered the original(?) question on how to handle the finish of ProcessSearchAsync
itself here.
To run tasks in parallel (as asked in the comments), here's your code modified, it gets a little complicated because of invoiceTypes == null
and _invoiceAccounts == null
checks. Note the way the checks are implemented below slightly changes the logic (previously it did WCF calls only if both _invoiceTypes and _invoiceAccounts were null - what if only one of them is null?):
private async Task ProcessSearchAsync()
{
Data.ConfigurationWCF confWcf = new Data.ConfigurationWCF();
Task</*typeof _invoiceTypes*/> t1;
Task</*typeof _invoiceAccounts*/> t2;
if (_invoiceTypes == null)
t1 = confWcf.GetInvoiceTypesAsync(MainForm.State.Entity);
else
{
var tsc1 = new TaskCompletionSource</*typeof _invoiceTypes*/>();
t1 = tsc1.Task;
tsc1.SetResult(_invoiceTypes);
}
if ( _invoiceAccounts == null )
t2 = confWcf.GetInvoiceAccountsAsync(MainForm.State.Entity);
else
{
var tsc2 = new TaskCompletionSource</*typeof _invoiceAccounts*/>();
t2 = tsc2.Task;
tsc2.SetResult(_invoiceAccounts);
}
DataSeekWCF seekWcf = new DataSeekWCF();
Task</*typeof _ds*/> t3 = seekWcf.SearchInvoiceAdminAsync(new Guid(cboEmployer.Value.ToString()), new Guid(cboGroup.Value.ToString()), txtSearchInvoiceNumber.Text, chkSearchLike.Checked, txtSearchFolio.Text, Convert.ToInt32(txtYear.Value));
await Task.WhenAll(new Task[] {t1, t2, t3});
_invoiceTypes = t1.Result;
_invoiceAccounts = t2.Result;
ds = t3.Result;
if (_ds != null)
{
SetupInvoiceGrid();
}
confWcf.Dispose();
seekWcf.Dispose();
}
Minor changes to what you've got there will do what you want. You can start new tasks and then do other stuff and then await just before you go on. As @Noseratio has helpfully pointed out, this snippet below isn't production-ready because I'm not checking for error conditions (like null
references, etc). The point is that you can succinctly and elegantly do these things in parallel without having to resort to using very much of the Tasks API. One adjustment I made worth pointing out is that you want to move the calls to Dispose
into the continuation (i.e., after all your await
s) because if you try to Dispose
right after calling the *Async methods you stand a good chance of killing off your WCF clients halfway through getting a response and the awaits
will probably wind up throwing exceptions (which I'm not catching).
private async void ProcessSearchAsync()
{
Data.ConfigurationWCF confWcf = new Data.ConfigurationWCF();
Task</*typeof _invoiceTypes*/> t1;
Task</*typeof _invoiceAccounts*/> t2;
// get catalogs on first search
if (_invoiceTypes == null && _invoiceAccounts == null)
{
t1 = confWcf.GetInvoiceTypesAsync(MainForm.State.Entity);
t2 = confWcf.GetInvoiceAccountsAsync(MainForm.State.Entity);
}
DataSeekWCF seekWcf = new DataSeekWCF();
Task</*typeof _ds*/> t3 = seekWcf.SearchInvoiceAdminAsync(new Guid(cboEmployer.Value.ToString()), new Guid(cboGroup.Value.ToString()), txtSearchInvoiceNumber.Text, chkSearchLike.Checked, txtSearchFolio.Text, Convert.ToInt32(txtYear.Value));
_invoiceTypes = await t1;
_invoiceAccounts = await t2;
_ds = await t3;
if (_ds != null)
{
SetupInvoiceGrid();
}
confWcf.Dispose();
seekWcf.Dispose();
}