【问题标题】:Can I specify which variables I want to persist beyond the completion of an await continuation?我可以指定在等待继续完成之后我想要保留哪些变量吗?
【发布时间】:2013-08-17 05:52:28
【问题描述】:

async 方法中,所有局部变量都被存储起来,这样当await 之后继续的任何线程都可以访问这些值。在await 之后有什么方法可以指出真正需要哪些值?

例如:

var firstName = "Karl";
var lastName = "Anderson";
var street1 = "123 Nowhere Street";
var street2 = "Apt 1-A";
var city = "Beverly Hills";
var state = "California";
var zip = "90210";

await MyTaskHere();

Console.WriteLine(firstName);
Console.WriteLine(city);

所以我声明了 7 个局部变量,但在 await 之后只使用其中的 2 个,是否有任何属性可以装饰我的变量以表明我打算只在await 完成了吗?

注意:这是一个人为的示例,但如果在下一个线程完成工作时不需要存储潜在的大数据块,那么它似乎是有益的。

【问题讨论】:

  • 我认为这件事最好留给编译器。

标签: c# multithreading async-await c#-5.0


【解决方案1】:

您可以在您的程序上运行Ildasm.exe 以查看编译器生成的代码。我尝试过这样做,但不幸的是我的 IL 技能有点欠缺,但是您可以看到所有局部变量都被捕获为生成的 <Foo>d__0 类的字段。鉴于此程序:

using System;
using System.Threading.Tasks;

namespace AsyncCaptureVariables
{
    class Program
    {
        public async Task Foo()
        {
            var firstName = "Karl";
            var lastName = "Anderson";
            var street1 = "123 Nowhere Street";
            var street2 = "Apt 1-A";
            var city = "Beverly Hills";
            var state = "California";
            var zip = "90210";

            await Task.Delay(5000);

            Console.WriteLine(firstName);
            Console.WriteLine(city);
        }

        public static void Main()
        {
            var program = new Program();
            Task t = program.Foo();
            t.Wait();
        }
    }
}

编译器生成如下部分转换为 C# 代码的内容:

using System;

class Program : System.Object
{
class <Foo>d__0 : System.ValueType, System.Runtime.CompilerServices.IAsyncStateMachine
  {   
    public int32 <>1__state;
    public System.Runtime.CompilerServices.AsyncTaskMethodBuilder <>t__builder;
    public class AsyncCaptureVariables.Program <>4__this;
    public string <firstName>5__1;
    public string <lastName>5__2;
    public string <street1>5__3;
    public string <street2>5__4;
    public string <city>5__5;
    public string <state>5__6;
    public string <zip>5__7;
    private System.Runtime.CompilerServices.TaskAwaiter <>u__$awaiter8;
    private object <>t__stack;

    void  MoveNext()
    {      
      try
      {      
        IL_0000:  ldc.i4.1
                IL_0001:  stloc.0
                IL_0002:  ldarg.0
                IL_0003:  ldfld      int32 AsyncCaptureVariables.Program/'<Foo>d__0'::'<>1__state'
                IL_0008:  stloc.2
                IL_0009:  ldloc.2
                IL_000a:  ldc.i4.s   -3
                IL_000c:  beq.s      IL_0014

                IL_000e:  ldloc.2
                IL_000f:  ldc.i4.0
                IL_0010:  beq.s      IL_0019

                IL_0012:  br.s       IL_001e

                IL_0014:  br         IL_00ee

                IL_0019:  br         IL_00a8

                IL_001e:  br.s       IL_0020

        //000009:         {
                IL_0020:  nop
        //000010:             var firstName = "Karl";
                IL_0021:  ldarg.0
                IL_0022:  ldstr      "Karl"
                IL_0027:  stfld      string AsyncCaptureVariables.Program/'<Foo>d__0'::'<firstName>5__1'
        //000011:             var lastName = "Anderson";
                IL_002c:  ldarg.0
                IL_002d:  ldstr      "Anderson"
                IL_0032:  stfld      string AsyncCaptureVariables.Program/'<Foo>d__0'::'<lastName>5__2'
        //000012:             var street1 = "123 Nowhere Street";
                IL_0037:  ldarg.0
                IL_0038:  ldstr      "123 Nowhere Street"
                IL_003d:  stfld      string AsyncCaptureVariables.Program/'<Foo>d__0'::'<street1>5__3'
        //000013:             var street2 = "Apt 1-A";
                IL_0042:  ldarg.0
                IL_0043:  ldstr      "Apt 1-A"
                IL_0048:  stfld      string AsyncCaptureVariables.Program/'<Foo>d__0'::'<street2>5__4'
        //000014:             var city = "Beverly Hills";
                IL_004d:  ldarg.0
                IL_004e:  ldstr      "Beverly Hills"
                IL_0053:  stfld      string AsyncCaptureVariables.Program/'<Foo>d__0'::'<city>5__5'
        //000015:             var state = "California";
                IL_0058:  ldarg.0
                IL_0059:  ldstr      "California"
                IL_005e:  stfld      string AsyncCaptureVariables.Program/'<Foo>d__0'::'<state>5__6'
        //000016:             var zip = "90210";
                IL_0063:  ldarg.0
                IL_0064:  ldstr      "90210"
                IL_0069:  stfld      string AsyncCaptureVariables.Program/'<Foo>d__0'::'<zip>5__7'
        //000017: 
        //000018:             await Task.Delay(5000);
                IL_006e:  ldc.i4     0x1388
                IL_0073:  call       class [mscorlib]System.Threading.Tasks.Task [mscorlib]System.Threading.Tasks.Task::Delay(int32)
                IL_0078:  callvirt   instance valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter [mscorlib]System.Threading.Tasks.Task::GetAwaiter()
                IL_007d:  stloc.3
        IL_0078:  callvirt   instance valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter [mscorlib]System.Threading.Tasks.Task::GetAwaiter()
        IL_007d:  stloc.3
        IL_007e:  ldloca.s   CS$0$0001
        IL_0080:  call       instance bool [mscorlib]System.Runtime.CompilerServices.TaskAwaiter::get_IsCompleted()
        IL_0085:  brtrue.s   IL_00c6

        IL_0087:  ldarg.0
        IL_0088:  ldc.i4.0
        IL_0089:  stfld      int32 AsyncCaptureVariables.Program/<Foo>d__0::<>1__state
        IL_008e:  ldarg.0
        IL_008f:  ldloc.3
        IL_0090:  stfld      valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter AsyncCaptureVariables.Program/<Foo>d__0::<>u__$awaiter8
        IL_0095:  ldarg.0
        IL_0096:  ldflda     valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder AsyncCaptureVariables.Program/<Foo>d__0::<>t__builder
        IL_009b:  ldloca.s   CS$0$0001
        IL_009d:  ldarg.0
        IL_009e:  call       instance void [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::AwaitUnsafeOnCompleted<valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter,valuetype AsyncCaptureVariables.Program/<Foo>d__0>(!!0&,
                                                                                                                                                                                                                                                         !!1&)
        IL_00a3:  nop
        IL_00a4:  ldc.i4.0
        IL_00a5:  stloc.0
        IL_00a6:  leave.s    IL_011d

        IL_00a8:  ldarg.0
        IL_00a9:  ldfld      valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter AsyncCaptureVariables.Program/<Foo>d__0::<>u__$awaiter8
        IL_00ae:  stloc.3
        IL_00af:  ldarg.0
        IL_00b0:  ldloca.s   CS$0$0002
        IL_00b2:  initobj    [mscorlib]System.Runtime.CompilerServices.TaskAwaiter
        IL_00b8:  ldloc.s    CS$0$0002
        IL_00ba:  stfld      valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter AsyncCaptureVariables.Program/<Foo>d__0::<>u__$awaiter8
        IL_00bf:  ldarg.0
        IL_00c0:  ldc.i4.m1
        IL_00c1:  stfld      int32 AsyncCaptureVariables.Program/<Foo>d__0::<>1__state
        IL_00c6:  ldloca.s   CS$0$0001
        IL_00c8:  call       instance void [mscorlib]System.Runtime.CompilerServices.TaskAwaiter::GetResult()
        IL_00cd:  nop
        IL_00ce:  ldloca.s   CS$0$0001
        IL_00d0:  initobj    [mscorlib]System.Runtime.CompilerServices.TaskAwaiter
//000019: 
//000020:             Console.WriteLine(firstName);
        IL_00d6:  ldarg.0
        IL_00d7:  ldfld      string AsyncCaptureVariables.Program/<Foo>d__0::<firstName>5__1
        IL_00dc:  call       void [mscorlib]System.Console::WriteLine(string)
        IL_00e1:  nop
//000021:             Console.WriteLine(city);
        IL_00e2:  ldarg.0
        IL_00e3:  ldfld      string AsyncCaptureVariables.Program/<Foo>d__0::<city>5__5
        IL_00e8:  call       void [mscorlib]System.Console::WriteLine(string)
        IL_00ed:  nop
//000022:         }
//000023: 
//000024:         public static void Main()
//000025:         {
//000026:             var program = new Program();
//000027:             Task t = program.Foo();
//000028:             t.Wait();
//000029:         }
//000030:     }
//000031: }
        IL_00ee:  leave.s    IL_0108

      }  // end .try
      catch [mscorlib]System.Exception 
      {
        IL_00f0:  stloc.1
        IL_00f1:  ldarg.0
        IL_00f2:  ldc.i4.s   -2
        IL_00f4:  stfld      int32 AsyncCaptureVariables.Program/<Foo>d__0::<>1__state
        IL_00f9:  ldarg.0
        IL_00fa:  ldflda     valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder AsyncCaptureVariables.Program/<Foo>d__0::<>t__builder
        IL_00ff:  ldloc.1
        IL_0100:  call       instance void [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::SetException(class [mscorlib]System.Exception)
        IL_0105:  nop
        IL_0106:  leave.s    IL_011d

      }  // end handler
      IL_0108:  nop
//000022:         }
      IL_0109:  ldarg.0
      IL_010a:  ldc.i4.s   -2
      IL_010c:  stfld      int32 AsyncCaptureVariables.Program/<Foo>d__0::<>1__state
//000023: 
//000024:         public static void Main()
//000025:         {
//000026:             var program = new Program();
//000027:             Task t = program.Foo();
//000028:             t.Wait();
//000029:         }
//000030:     }
//000031: }
      IL_0111:  ldarg.0
      IL_0112:  ldflda     valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder AsyncCaptureVariables.Program/<Foo>d__0::<>t__builder
      IL_0117:  call       instance void [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::SetResult()
      IL_011c:  nop
      IL_011d:  nop
      IL_011e:  ret
    } // end of method <Foo>d__0::MoveNext

    .method private hidebysig newslot virtual final 
            instance void  SetStateMachine(class [mscorlib]System.Runtime.CompilerServices.IAsyncStateMachine param0) cil managed
    {
      .custom instance void [mscorlib]System.Diagnostics.DebuggerHiddenAttribute::.ctor() = ( 01 00 00 00 ) 
      .override [mscorlib]System.Runtime.CompilerServices.IAsyncStateMachine::SetStateMachine
      // Code size       13 (0xd)
      .maxstack  8
      IL_0000:  ldarg.0
      IL_0001:  ldflda     valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder AsyncCaptureVariables.Program/<Foo>d__0::<>t__builder
      IL_0006:  ldarg.1
      IL_0007:  call       instance void [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::SetStateMachine(class [mscorlib]System.Runtime.CompilerServices.IAsyncStateMachine)
      IL_000c:  ret
    } // end of method <Foo>d__0::SetStateMachine

  } // end of class <Foo>d__0

【讨论】:

  • +1 详细了解幕后发生的事情,没有意识到使用状态机来存储值。
【解决方案2】:

您可以通过在代码中添加大括号来创建作用域,但是当不再引用某些内容时,编译器无论如何都会收集它。这样做

{  //start of scope
    var firstName = "Karl";
    var lastName = "Anderson";
    var street1 = "123 Nowhere Street";
    var street2 = "Apt 1-A";
    var city = "Beverly Hills";
    var state = "California";
    var zip = "90210";

    await MyTaskHere();
}//end of scope

还有其他一些事情可能需要考虑,比如你是否可以创建方法来逻辑划分事物,而不是我认为这是一个更好的选择。虽然这样做也没有坏处。

【讨论】:

  • 添加范围不会导致任何内容设置为空。当地人甚至不再是真正的当地人了。它们被转换为编译器生成的IAsyncStateMachine 实现上的字段。
  • 让我们这样称呼它,因为变量将在右括号之后超出范围,因此它们将有利于 GC 被收集。
  • {string test = "abc";} Console.WriteLine(test);试试这个,让我有你的想法
  • 范围是关于在程序文本中可以通过名称引用某个实体的位置。这与某些参考的内容符合收集条件时完全不同。您的示例无法编译,因此我不确定它证明了什么。试试这个Func&lt;string&gt; f; { string x = "abc"; f = () =&gt; x; } Console.WriteLine(f());。您是否声称x ("abc") 引用的字符串将以某种方式在} 处被垃圾收集?因为这似乎是您对范围所做的声明,这是错误的。
  • 实际上,这行不通,至少对于当前的编译器不行。该变量将不可访问,但状态机的字段不会设置为null,因此对象不会被GC。
【解决方案3】:

不,你不能。 (除了将它们拆分为单独的方法或将它们设置为null 的明显解决方案。

编译器在这种情况下没有完全优化;它可能会捕获比它需要的更多的变量,并且可能会比需要的时间更长。这可能是微软未来会优化的东西。

【讨论】:

  • 也许他们会在 C# 6 中解决这个问题,我们拭目以待。 :-)
猜你喜欢
  • 2010-12-18
  • 2018-08-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多