简体   繁体   English

通过使用Linq延迟执行与异步/等待

[英]Stung by using Linq deferred execution with async/await

I am new to async/await and am tinkering with it to execute an operation on a list of objects using a list of tasks. 我是异步/等待的新手,正在尝试使用任务列表对对象列表执行操作。 I used Linq to generate both the list of objects and the list of tasks. 我使用Linq来生成对象列表和任务列表。 The example below looks a little contrived, but it is a simplified version of my actual code. 下面的示例看起来有些虚构,但这是我实际代码的简化版本。

I found that when the code is executed as shown, after all tasks have completed (after the await), none of the object's Now properties have been updated, and all of the tasks still have a status of Running. 我发现,如图所示执行代码时,在所有任务完成之后(在等待之后),该对象的Now属性均未更新,并且所有任务的状态仍为Running。

I found that by eliminating Linq deferred execution by converting both objects and tasks to actual lists via .ToList<>(), my code worked as expected (objects populated, tasks all run to completion). 我发现通过通过.ToList <>()将对象和任务都转换为实际列表来消除Linq延迟执行,我的代码按预期工作(对象已填充,任务全部运行完毕)。

I am familiar with Linq deferred execution but I'm really confused by what is (isn't) going on in this code. 我对Linq延迟执行很熟悉,但是我对代码中发生的事情(不是)感到困惑。 I'm probably making a noob mistake with async/await...what is it? 我可能在用async / await犯了一个noob错误...这是什么?

private class Foo {
    public DateTime Now { get; set; }
}

private void Button_Click( object sender, EventArgs e ) {
    PopulateDates();
}

private async void PopulateDates() {
    var ordinals = new List<int>() { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, };

    var foos = ordinals.Select( o => new Foo() ); //.ToList();

    var tasks = foos.Select( f => PopulateDateAsync( f ) ); //.ToList();

    await Task.WhenAll( tasks );

    var firstNow = foos.ElementAt( 0 ).Now;
    var firstTaskStatus = tasks.ElementAt( 0 ).Status;
}

private Task PopulateDateAsync( Foo foo ) {
    return Task.Run( () => PopulateDate( foo ) );
}

private void PopulateDate( Foo foo ) {
    Thread.Sleep( 2000 );
    foo.Now = DateTime.Now;
}

Your problem is due to LINQ's deferred execution. 您的问题是由于LINQ的执行延迟。 In particular, Task.WhenAll is appropriately waiting for the tasks to complete. 特别是Task.WhenAll正适当地等待任务完成。 However, when you call ElementAt , the sequence is re-evaluated, creating a new Foo and Task . 但是,当您调用ElementAt ,将重新评估序列,从而创建一个新的FooTask

So, this would also not work: 因此,这也不起作用:

var ordinals = new List<int>() { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, };

var foos = ordinals.Select( o => new Foo() ); //.ToList();

// Get the first Foo, creating it.
var first = foos.ElementAt(0);

// This gets a *different* Foo. It creates it again.
var other = foos.ElementAt(0);

MessageBox.Show((first == other).ToString()); // Displays "false"

In general, it's a good idea to "reify" your sequence (using ToArray or similar) when dealing with any operations with side effects, including starting async operations. 通常,在处理任何有副作用的操作(包括启动async操作)时,最好“调整”序列(使用ToArray或类似方法)。 Task.WhenAll will reify your sequence internally, but then if you evaluate it again (eg, ElementAt ), you get unexpected behavior. Task.WhenAll将在内部对序列进行序列化,但是如果再次对其进行评估(例如ElementAt ),则会出现意外行为。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM