【问题标题】:C# How to make sure one async method executes after another async methodC#如何确保一个异步方法在另一个异步方法之后执行
【发布时间】:2018-01-06 08:43:03
【问题描述】:

我看过一些关于asyncawait 的帖子以及它们的实际工作原理,但我还是有点困惑。假设我有两个 async 方法,我想确保第二个在第一个完成后开始。例如考虑这样的事情:

    public async Task MyMethod(Item x)
    {
        await AddRequest(x); // 1. Add an item asynchronously 

        // make sure 2 starts after 1 completes

        await GetAllRequest(); // 2. get all items asynchronously 
    }

那么,确保发生这种情况的正确方法是什么?

更新:

为了尝试提供一个最小、完整和可验证的示例:

我在 WPF 应用程序中有 2 个 WCF 服务来与 Oracle WebCenter Content(UCM) 通信。这是我背后代码的最小版本:

UCM 服务添加新客户:

    public static async Task<ServiceResult> CheckInCustomer(Customer c)
    {
        CheckInSoapClient client = new CheckInSoapClient();
        using (OperationContextScope scope = new OperationContextScope(client.InnerChannel))
        {
                // an async method in Oracle WebContent services to insert new content
                result = await client.CheckInUniversalAsync(c);
        }
        return new ServiceResult();
    }

UCM 服务获取所有客户:

    public static async Task<Tuple<ServiceResult, QuickSearchResponse>> GetAllCustomers()
    {
        ServiceResult error;
        var result = new QuickSearchResponse();
        SearchSoapClient client = new SearchSoapClient();
        using (OperationContextScope scope = new OperationContextScope(client.InnerChannel))
        {
                // an async method in Oracle WebContent services to search for contents
                result = await client.QuickSearchAsync(queryString);
        }
            return new Tuple<ServiceResult, QuickSearchResponse>(error, result);
    }

在 UI 中添加绑定到按钮命令的客户异步方法:

    private async Task AddCustomer()
    {
        var result = await CheckInCustomer(NewCustomer);
        if (!result.HasError)
            await GetAllCustomers();
    }

    public ICommand AddCustomerCommand
    {
        get
        {
            _addCustomerCommand = new RelayCommand(async param => await AddCustomer(), null);
        }
    }

获取所有客户异步方法(Items 绑定到 UI 中的 DataGrid):

    private async Task GetAllCustomers()
    {
        Items.Clear();
        var searchResult = await GetCustomersInfoAsync();
        if (!searchResult.HasError)
        {
            foreach (var item in searchResult)
                Items.Add(new CustomerVm(item));
        }
    }

现在,当我添加一个新的Customer 时,我希望在我首先插入客户然后获取所有客户时在DataGrid 中看到新创建的项目。但是这段代码的行为是随机的,这意味着有时列表会在插入几秒钟后显示新创建的客户,有时则不会。

【问题讨论】:

  • 使用await后你不会收到任务,而是任务的结果。 await异步等待直到任务完成。
  • 据我所知await 不会等待相关调用完成,如果您想确保完成t1 然后t2 开始,最好使用Wait()
  • @Aria 不正确。 await 顾名思义异步等待任务完成。不信请看this example
  • @FCin:异步等待是什么意思?
  • @Bahman_Aries 我的意思是您的任务将一个接一个地执行而不会阻塞。这意味着您不会失去应用程序的响应能力。

标签: c# wpf asynchronous


【解决方案1】:

您提供的代码实际上可以满足您的需求。

public async Task MyMethod(Item x)
{
    await AddRequestAsync(x); 

    await GetAllRequestAsync(); 
}

但是,如果您谈论的是它们之间不可预测的“非常非常短”的延迟,并且您想确保没有延迟,那是不可能的。

【讨论】:

  • 感谢您的回答,但这对我现在不起作用,第二行有时会先运行。如果我正确理解了await,它会将方法的剩余部分(即AddRequestAsync)的运行时间安排为另一次,然后将控制权返回给调用方法(即MyMethod)。因此,第二个方法 (GetAllRequestAsync) 的某些部分实际上可以在第一个方法完成之前运行。对吗?
  • @Bahman_Aries:不,这不正确。如果您认为这就是您所看到的,您应该在问题中提供minimal reproducible example
  • “有时第二行先运行”——这一定是一个错误的结论。如果您的意思是 GetAllRequest 不包含先前添加的请求,那就另当别论了。也许AddRequest 工作在fire and forget 模式并在实际处理请求之前返回。但是如果没有看到实际的实现,真的很难说。
  • @taffer,我相信你对错误结论是正确的,但我不知道我做错了什么。我提供了更多细节。
  • @Bahman_Aries:我不同意。您只需要取出数据库部分并用硬编码数据替换它们。这种“分而治之”来找出问题所在是软件工程的核心部分——并且绝对是在 Stack Overflow 上提出一个好问题的核心部分。
【解决方案2】:

Await - (首选,必须在 ContinueWith 上使用)(TY 到 @Taffer 和 @Michael cmets)等待新创建的任务完成,并确保在等待任务执行完成后继续执行。

您的第一个 func AddRequest 将在移至 GetAllRequest 之前提供结果。

public async Task MyMethod(Item x)
{
    await AddRequest(x);
    await GetAllRequest();
}

这里我不明白的是异步是指第一个 方法可以保留未完成并执行主要任务 继续。所以第二种方法的某些部分可能会运行 在第一种方法完成之前。

之所以称为异步编程,是因为运行时会在遇到 await 关键字(类似于迭代器中的 yield)时捕获程序的状态,并在等待的任务完成后恢复状态,以便继续在正确的上下文中运行。..

但是如果你只想在第一个任务之后运行它,这是你可以做的。

使用 ContinueWith - 它的方法可用于任务,允许在任务完成执行后执行代码。简而言之,它允许继续。

public async Task MyMethod(Item x)
{
    var req = AddRequest(x).ContinueWith(async (t1) =>
               {
                  await GetAllRequest();
               }); 
}

【讨论】:

  • 由于 .NET 4.5 ContinueWith 已被弃用,取而代之的是 await
  • @taffer Await 受到青睐,但我正在开发 .NET 4.5.2,我没有在文档中看到任何警告或任何提及它,我们在一些地方使用 continue with我们的项目。
  • @Hey24sheep 你仍然可以使用 ContinueWith 但最好只调用 await。
  • @MichaelPuckettII 哦,好吧,是的,我知道 await 是首选。谢谢
猜你喜欢
  • 2012-02-09
  • 1970-01-01
  • 2014-09-13
  • 1970-01-01
  • 1970-01-01
  • 2017-04-27
  • 2015-11-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多