通过使用带有async/await的Linq延迟执行来阻止它

Dar*_*ryl 2 c# linq deferred-execution async-await

我是async/await的新手,并且正在修改它以使用任务列表对对象列表执行操作.我使用Linq生成对象列表和任务列表.下面的示例看起来有点人为,但它是我实际代码的简化版本.

我发现当代码如图所示执行时,在所有任务完成之后(等待之后),对象的Now属性都没有更新,并且所有任务的状态仍为Running.

我发现通过.ToList <>()将对象和任务转换为实际列表来消除Linq延迟执行,我的代码按预期工作(填充对象,任务全部运行完成).

我熟悉Linq延迟执行,但我真的很困惑这个代码中发生了什么(不是).我可能在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;
}
Run Code Online (Sandbox Code Playgroud)

Ste*_*ary 6

您的问题是由于LINQ的延迟执行.特别是,Task.WhenAll正在等待任务完成.但是,当您调用时ElementAt,将重新评估序列,创建一个新的FooTask.

所以,这也行不通:

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"
Run Code Online (Sandbox Code Playgroud)

通常,ToArray在处理任何具有副作用的async操作(包括启动操作)时,"重新启动"序列(使用或类似)是个好主意.Task.WhenAll将在内部重新启动您的序列,但如果您再次评估它(例如,ElementAt),您会得到意外的行为.