等待所有任务完成后再运行代码的方法

4

我正在尝试编写一个多线程搜索,然后在任务完成后显示所有结果,但目前我不知道在所有任务完成后如何处理结果。

我的代码如下:

    private async void DoSearchAsync()
    {
        var productResults = await SearchProductsAsync(CoreCache.AllProducts);
        var brochureResults = await SearchBrochuresAsync(CoreCache.AllBrochures);

        _searchResults.AddRange(productResults); 
        _searchResults.AddRange(brochureResults);

        ResultsCount = _searchResults.Count;
    }

在这里,_searchResults 是一个 List<SearchResult>

我的理解是它会同时执行两个 await 并将产品添加到搜索结果中。然而,当我在我的控制器中调用它时:

    public ActionResult Index(string searchText)
    {
        SearchHelper helper = new SearchHelper(searchText);
        helper.DoSearchAsync();

        return View(helper);
    }

它在搜索完成之前显示页面,因此没有结果显示。如何使其在显示页面之前等待结果完成?

我已经研究了 Tasks.Wait,但不知道如何应用于上述内容,因为它需要一个任务数组。

    private Task<List<SearchResult>> SearchProductsAsync(IEnumerable<Product> products)
    {
        return Task<List<SearchResult>>.Factory.StartNew(() => GetProducts(products));
    }

    private Task<List<SearchResult>> SearchBrochuresAsync(IEnumerable<Assets> brochures)
    {
        return Task<List<SearchResult>>.Factory.StartNew(() => GetBrochures(brochures));
    }

你能发布SearchProductsAsync和SearchBrochureAsync的方法签名吗? - Baldy
@Baldy 方法已按要求添加 - Pete
我不确定我是否理解正确,但是Task<List<SearchResult>>不是任务数组,它是一个返回List<SearchResult>列表的单个任务,您可以在等待后调用Result属性以获取所需的结果。http://msdn.microsoft.com/pt-br/library/dd321468(v=vs.110).aspx - Eduardo Wada
@EduardoWada 是的,每个任务都会返回一个搜索结果列表,当所有任务完成后,我想将每个列表的范围添加到“_searchResults”中。 - Pete
你可以逐个调用所有任务的等待函数,然后访问它们的结果。如果需要同步,按任意顺序等待它们应该没有问题。 - Eduardo Wada
你几乎永远不想在ASP.NET上使用 Task.Factory.StartNewGetProducts 函数是在做什么? - Stephen Cleary
5个回答

14

每次在ASP.NET控制器中调用Factory.StartNewTask.Run时,您都会从ThreadPool中获取一个线程。该线程可能正在为另一个传入的HTTP请求提供服务。因此,您真正损害了Web应用程序的可伸缩性。这可能是一个严重问题,具体取决于您的Web应用程序预期收到的并发HTTP请求数量。

如果您接受这一点,代码看起来可能像这样:

private async Task DoSearchAsync()
{
    var productResults = SearchProductsAsync(CoreCache.AllProducts);
    var brochureResults = SearchBrochuresAsync(CoreCache.AllBrochures);

    await Task.WhenAll(productResults, brochureResults);

    _searchResults.AddRange(productResults.Result); 
    _searchResults.AddRange(brochureResultsbrochure.Results);

    ResultsCount = _searchResults.Count;
}

public async Task<ActionResult> Index(string searchText)
{
    SearchHelper helper = new SearchHelper(searchText);

    await helper.DoSearchAsync();

    return View(helper);
}

请注意,我将DoSearchAsync中的async void更改为async Task,并使你的控制器方法async,因此它返回Task<ActionResult>


可扩展性不是问题。这就是我最终所做的事情(+1,但由于我使用了Eduardo的答案来到这里,因此未被接受)-但我没有使用异步方式,而是使用Task.WaitAll,然后处理结果。WaitAll和WhenAll之间有什么区别? - Pete
1
@Pete,差别很大,但这是一个单独问题的主题,而不是评论(我相信在SO上之前已经有人回答过)。总的来说,在ASP.NET代码中应避免使用.Wait()/.Result阻塞。请查看Stephen的博客:http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html - noseratio - open to work
其实是为了评论 - 我在寻找Task.result,这样我就可以处理数组中的任务,并在调用WaitAll后获取这些任务的结果,但出于某种原因,Microsoft网站上没有展示.results的示例。 - Pete
2
@Pete,这并不是await Task.WhenAllTask.WaitAll之间的区别。如果你想在评论格式中适配它,那么区别就在于异步继续与同步阻塞。 - noseratio - open to work
3
@Pete,在我看来,你应该无论如何都要更改你的采纳答案。Noseratio的回答是正确和值得采纳的,而Eduardo的回答则不然。即使更好的答案后来才出现,将采纳答案更改为更好的答案难道不是合适的吗?特别是因为浏览Eduardo的回答会导致您形成一个非常糟糕的模式 - 为了I/O绑定的工作启动新线程是不良实践,尤其是现在使用异步调用同样容易的情况下。 - Luaan

1
我的理解是它会同时执行两个等待操作。
这是不正确的,Pete。await的意思是暂停当前方法的执行,直到被调用的异步方法完成。所以你的两个搜索不会并行运行。
为了澄清,请参见MSDN文档的第一行....
应该使用Task.WhenAll等待两个搜索完成。由于WhenAll支持返回Task<T>,而且你的两个异步搜索都返回List<SearchResult>,因此可以将两个搜索的结果合并,并在一个语句中等待它们。
    var searchProducts = Task.Factory.StartNew(() => GetProducts(CoreCache.AllProducts));
    var searchBrochure = Task.Factory.StartNew(() => GetBrochures(CoreCache.AllBrochures));
    var allResults = await Task.WhenAll(new [] { searchProducts, searchBrochure });
    //allResults is List<SearchResult>[]

为了完整起见,值得注意的是allResults将包含2个结果集。它不会对这两个集合执行联合操作。

LinqPad的一个完整工作示例可以在GitHub上找到此处


啊,是的,这就是我最终使用的方法。+1 是为了关于 await 的额外信息 - 如果我按顺序等待它们,那就不需要多线程了! - Pete
1
@Baldy,在你编辑了答案之后,对于allResults你的建议变得不太清晰了。毕竟你是在建议像被接受的答案中那样执行allResults.Wait()吗? - noseratio - open to work

0

你可以通过让函数不是异步的来等待结果完成后再显示页面。

或者,如果你想使用异步,你可以将搜索函数移动到在页面加载时触发的 AJAX 调用中。


虽然这样做会失去并行处理的优势,如果需要的话。 - Eduardo Wada
实际上,我已经添加了一个替代方案。 - Starscream1984
这完全违背了我想要的后端多线程使用,也意味着对于任何关闭js的人来说,我的网站搜索都无法工作。 - Pete

0

如果我按照他的方式去做,例如:Task task = Task<List<SearchResult>>.Factory.StartNew(() => GetBrochures(brochures)); task.wait(),那么我该如何从task中获取实际结果呢? - Pete
我现在没有编译器,但是根据我所读的,应该有一个Result属性:task.Result; 但是请将您的声明更改为Task<List<SearchResult>> task = Task<List<SearchResult>>.Factory.StartNew(() => { return GetBrochures(brochures); }); 或者将其转换为它。 - Eduardo Wada
那个 task.Result 改变了一切!这让事情变得更简单,因为我只需要创建一个任务数组,然后在执行 .WaitAll 后处理它们。谢谢! - Pete
1
不是使用一个线程,而是使用两个线程来完成相同的工作,这会增加额外的开销。一个线程被阻塞等待另一个线程,绝不比同步调用“DoSomeWork”更好。 - Kris Vandermotten
当然,这段代码只是一个示例,在实际情况下可能会有多个任务。 - Eduardo Wada

0

你需要等待函数完成。为了做到这一点,首先要让它返回一个任务:

private async Task DoSearchAsync()

然后等待它:

public async Task<ActionResult> Index(string searchText)
{
    SearchHelper helper = new SearchHelper(searchText);
    await helper.DoSearchAsync();

    return View(helper);
}

更多信息,请参见http://www.asp.net/mvc/tutorials/mvc-4/using-asynchronous-methods-in-aspnet-mvc-4


这需要从DoSearchAsync()返回一个任务,但它目前没有这样做。 - Pete
Pete,await语句会为您解开任务。要完成TAP的实现,您应该从DoSearchAsync()返回一个任务。 - Baldy
@Pete 不需要对 DoSearchAsync 的主体进行任何更改。 - Kris Vandermotten

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