如何等待await/async方法完成

4

我有以下的异步方法:

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

我不希望在_invoiceTypes、_invoiceAccounts和_ds完成之前执行SetupInvoiceGrid。

有什么线索吗?我做对了吗?我应该使用Task而不是await吗?


我想出了这段代码,看起来运行正常并且看起来很好,但我不确定它是否正确:

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

你试过运行这个程序看看它是否能正常工作吗?在我看来,它看起来很好。 - Ben Collins
1
这个代码是可行的.. 但是我想让GetInvoiceTypesAsync和GetInvoiceAccountsAsync与SearchInvoiceAdminAsync并行运行。所以这三个方法是并行执行的,但在它们全部完成之前我不能执行SetupInvoiceGrid。 - VAAA
2个回答

3

我在这里回答了关于如何处理ProcessSearchAsync完成的原始问题。

要并行运行任务(如评论中所询问的),这是修改后的代码,由于需要检查invoiceTypes == null_invoiceAccounts == null,代码有点复杂。注意下面实现检查的方式略微改变了逻辑(之前只有当_invoiceTypes和_invoiceAccounts都为null时才进行WCF调用 - 如果它们中只有一个为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();
}

1
我想要同时运行GetInvoiceTypesAsync和GetInvoiceAccountsAsync以及SearchInvoiceAdminAsync。所以这三个任务是并行执行的,但在它们全部完成之前,我无法执行SetupInvoiceGrid。 - VAAA
ProcessSearchAsync是我拥有的相同方法,但返回Task而不是void? - VAAA
我已经更新了问题,附上了我刚修改过的代码,看起来可以并行工作。如果您认为正确,请告诉我。 - VAAA
1
不,它们并没有并行运行:_invoiceTypes = await t1; _invoiceAccounts = await t2; _ds = await t3; 而是串行运行:你首先等待t1,然后是t2,最后是t3。尝试这样做:await Task.WhenAll(new Task[] {t1, t2, t3}); _invoiceTypes = t1.Result; _invoiceAccounts = t2.Result; ds = t3.Result; - avo
1
这个问题更加复杂,因为你最初写的代码中有像 invoiceTypes == null_invoiceAccounts == null 这样的检查。请参考新版本。 - avo
显示剩余4条评论

0

你只需要对你已有的代码进行微小的修改就可以实现你想要的功能。你可以开始新的任务,然后做其他的事情,在继续之前等待。正如 @Noseratio 所指出的那样,下面的片段不是生产就绪的,因为我没有检查错误条件(比如null引用等)。关键在于,你可以简洁而优雅地并行执行这些操作,而无需使用大量的Tasks API。我进行的一个调整值得一提的是,你希望将对Dispose的调用移动到连续部分(即,在所有的await之后),因为如果你在调用 *Async 方法后立即调用 Dispose,你很有可能在获取响应期间中断你的 WCF 客户端,并且 awaits 可能最终会抛出异常(我没有捕获)。

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

你觉得我刚才在问题上编辑的更新代码还可以吗?非常感谢。 - VAAA
是的,我认为你的更新基本上是正确的。我认为使用 Task.WhenAll 使你的语法有点冗长,但应该没问题。 - Ben Collins
1
抱歉,这个不对。首先,如果_invoiceTypes |= null_invoiceAccounts |= null,那么会出错,因为t1t2会保持未初始化状态。其次,任务t1t2t3并不能同时运行,它们在此处是顺序执行的。第三,如果_invoiceTypes_invoiceAccounts成员变量一开始就非空,难道不应该重复使用吗?我认为@avo的答案更正确。 - noseratio - open to work
1
@Noseratio,没错,你(部分)是对的。我编辑了我的答案几次,其中一次修改中有一两个句子说“你需要做更多的错误检查”,或者类似的话。我必须在晚上离开之前先离开,没有机会像我想要的那样把它整理出来。然而,我关于t1t2t3并行运行是正确的。这些任务应该由*Async方法启动。像我上面那样按顺序调用await并不意味着它们串行运行,而是意味着我们等待它们按顺序完成,这也是你必须做的。 - Ben Collins
@BenCollins,你完全正确,它们在两种情况下都是并行运行的。实际上,当时已经很晚了,我忽略了这个事实,即它是start、start、start/await、await、await,而不是start/await、start/await、start/await。看起来,Avo在他对答案的评论中也发表了类似的错误声明。 - noseratio - open to work

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接