4

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();
}
abatishchev
  • 98,240
  • 88
  • 296
  • 433
VAAA
  • 14,531
  • 28
  • 130
  • 253
  • Have you tried running this to see if it works? It looks fine to me. – Ben Collins Aug 17 '13 at 17:27
  • 1
    this works.. but I would like for example to run in parallel GetInvoiceTypesAsync and GetInvoiceAccountsAsync with SearchInvoiceAdminAsync. So the 3 moths run pararell but I cant execute SetupInvoiceGrid until all of them are finished. – VAAA Aug 17 '13 at 17:30

2 Answers2

3

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();
}
Community
  • 1
  • 1
avo
  • 10,101
  • 13
  • 53
  • 81
  • 1
    I would like for example to run in parallel GetInvoiceTypesAsync and GetInvoiceAccountsAsync with SearchInvoiceAdminAsync. So the 3 moths run pararell but I cant execute SetupInvoiceGrid until all of them are finished. – VAAA Aug 17 '13 at 17:31
  • ProcessSearchAsync is the same method that I have but returning Task instead of void? – VAAA Aug 17 '13 at 17:33
  • Nope, `ProcessSearchAsync` is just how you should name it by convention when you declare it `async`. I'll show how run tasks in parallel in the answer. – avo Aug 17 '13 at 17:40
  • I have updated the question with a code that I just modified and seems working with pararell. Let me know if you think is correct please. – VAAA Aug 17 '13 at 17:47
  • 1
    no, they're not running in parallel: _invoiceTypes = await t1; _invoiceAccounts = await t2; _ds = await t3; , rather in serial: you first await for t1, then t2, then t3. Try this: `await Task.WhenAll(new Task[] {t1, t2, t3}); _invoiceTypes = t1.Result; _invoiceAccounts = t2.Result; ds = t3.Result; ` – avo Aug 17 '13 at 18:23
  • 1
    It is even more involved than that, because of checks like `invoiceTypes == null` and `_invoiceAccounts == null` which you had originally. See the new edition. – avo Aug 17 '13 at 18:59
  • I was wrong about serial execution, as @BenCollins pointed out below. In both cases, tasks run in parallel. – avo Aug 19 '13 at 08:33
  • @VAAA, may I ask what was wrong with the answer itself, so that you have unaccepted it? IMHO, the answer stands perfectly correct. I even clarified my thoughts on the preference over `Task.WhenAll` [here](http://stackoverflow.com/questions/18310996/why-should-i-prefer-single-await-task-whenall-over-multiple-awaits?lq=1). – avo Aug 19 '13 at 13:40
  • Sorry I did a mistake... I fix the answer @avo – VAAA Aug 19 '13 at 13:42
0

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 awaits) 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();
}
Ben Collins
  • 20,538
  • 18
  • 127
  • 187
  • Do you think the updated code I just edit on the question is ok? Thanks a lot – VAAA Aug 17 '13 at 17:55
  • Yeah, I think your updates look basically right. I think using `Task.WhenAll` makes your syntax a bit more verbose than it really has to be, but it should be fine. – Ben Collins Aug 17 '13 at 17:57
  • 1
    Sorry, this is wrong. Firstly, if `_invoiceTypes |= null` or `_invoiceAccounts |= null`, it would fault because `t1` and `t2` would be left uninitialized. Second, the tasks t1, t2, t3 do not run in parallel with each other, they rather do sequentially here. Third, wasn't there a goal to reuse _invoiceTypes and _invoiceAccounts members if they are non-null at the beginning? The answer by @avo appears to be more correct to me. – noseratio Aug 18 '13 at 01:05
  • 1
    @Noseratio yep, you're (partly) right. I edited my answer a few times, and one of my revisions had a sentence or two in there saying "you'll want to do more error checking", or something to that effect. I had to get up and leave for the evening before I had a chance to iron it out like I had wanted to. However, I *am* correct about `t1`,`t2`,and `t3` running in parallel. The tasks should have been started by the *Async methods. Calling `await` in series like I did above doesn't mean they run serially, it means we wait for them to finish in sequence, which is what you have to do anyway. – Ben Collins Aug 18 '13 at 04:35
  • @BenCollins, you're totally correct, they are running in parallel in both cases. Indeed, it was late and I overlooked the fact it's *start,start,start/await,await,await* rather than *start/await,start/await,start/await*. It appears, avo made a similar false statement in his comments to the answer. – noseratio Aug 19 '13 at 07:14