使用和不使用异步声明的lambdas之间是否存在差异

时间:2016-05-10 08:34:49

标签: c# asynchronous lambda async-await

lambdas () => DoSomethingAsync()async () => await DoSomethingAsync()两者都被列为Func<Task>时有区别吗?我们应该选择哪一个?何时?

这是一个简单的控制台应用

using System;
using System.Threading.Tasks;

namespace asyncDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            var demo = new AsyncDemo();
            var task = demo.RunTheDemo();
            task.Wait();

            Console.ReadLine();
        }
    }

    public class AsyncDemo
    { 
        public async Task Runner(Func<Task> action)
        {
            Console.WriteLine(DateTime.Now.ToLongTimeString() + " Launching the action");
            await action();
        }

        private async Task DoSomethingAsync(string suffix)
        {
            await Task.Delay(2000);
            Console.WriteLine(DateTime.Now.ToLongTimeString() + " Done something, " + suffix);
        }

        public async Task RunTheDemo()
        {
            await Runner(() => DoSomethingAsync("no await"));
            await Runner(async () => await DoSomethingAsync("with await"));
        }
    }
}

输出结果为:

09:31:08 Launching the action
09:31:10 Done something, no await
09:31:10 Launching the action
09:31:12 Done something, with await

所以在RunTheDemo中,对await Runner(someLambda);的两次调用似乎都采用相同的时序特性做同样的事情 - 两者都有正确的两秒延迟。

这两条线都有效,它们完全相同吗? () => DoSomethingAsync()async () => await DoSomethingAsync()结构之间有什么区别?我们应该选择哪一个?何时?

这与“我应该在一般情况下使用await”这个问题不同,因为我们正在处理工作异步代码,其中lambdas键入为Func<Task>,这些内容正在等待消费方法。问题涉及如何宣布这些lambda,以及该声明的效果是什么。

3 个答案:

答案 0 :(得分:20)

  

使用和不使用异步

声明的lambda之间是否存在差异

是的,有区别。一个是异步lambda,另一个是返回任务的lambda。

async lambda被编译成状态机,而另一个并不是这样,因为异步lambda具有不同的异常语义,因为异常被封装在返回的任务中并且不能同步抛出。

它与常规方法完全相同。例如,在此异步方法之间:

public class Door : MonoBehaviour
{
    public float ySensitivity = 300f;
    public float frontOpenPosLimit = 45;
    public float backOpenPosLimit = 45;

    public GameObject frontDoorCollider;
    public GameObject backDoorCollider;

    bool moveDoor = false;
    DoorCollision doorCollision = DoorCollision.NONE;


    // Use this for initialization
    void Start()
    {
        StartCoroutine(doorMover());
    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            Debug.Log("Mouse down");

            RaycastHit hitInfo = new RaycastHit();
            if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out hitInfo))
            {
                if (hitInfo.collider.gameObject == frontDoorCollider)
                {
                    moveDoor = true;
                    Debug.Log("Front door hit");
                    doorCollision = DoorCollision.FRONT;
                }
                else if (hitInfo.collider.gameObject == backDoorCollider)
                {
                    moveDoor = true;
                    Debug.Log("Back door hit");
                    doorCollision = DoorCollision.BACK;
                }
                else
                {
                    doorCollision = DoorCollision.NONE;
                }
            }
        }

        if (Input.GetMouseButtonUp(0))
        {
            moveDoor = false;
            Debug.Log("Mouse up");
        }
    }

    IEnumerator doorMover()
    {
        bool stoppedBefore = false;
        float yRot = 0;

        while (true)
        {
            if (moveDoor)
            {
                stoppedBefore = false;
                Debug.Log("Moving Door");

                yRot += Input.GetAxis("Mouse Y") * ySensitivity * Time.deltaTime;


                //Check if this is front door or back
                if (doorCollision == DoorCollision.FRONT)
                {
                    Debug.Log("Pull Down(PULL TOWARDS)");
                    yRot = Mathf.Clamp(yRot, -frontOpenPosLimit, 0);
                    Debug.Log(yRot);
                    transform.localEulerAngles = new Vector3(0, -yRot, 0);
                }
                else if (doorCollision == DoorCollision.BACK)
                {
                    Debug.Log("Pull Up(PUSH AWAY)");
                    yRot = Mathf.Clamp(yRot, 0, backOpenPosLimit);
                    Debug.Log(yRot);
                    transform.localEulerAngles = new Vector3(0, yRot, 0);
                }
            }
            else
            {
                if (!stoppedBefore)
                {
                    stoppedBefore = true;
                    Debug.Log("Stopped Moving Door");
                }
            }

            yield return null;
        }

    }


    enum DoorCollision
    {
        NONE, FRONT, BACK
    }
}

这个任务返回方法:

async Task FooAsync()
{
    await DoSomethingAsync("with await");
}

观察这些方法可以更清楚地显示差异,但由于lambda只是语法糖,实际上编译成与这些方法相同的方法。

  

我们应该选择哪一个?何时?

这真的取决于你的口味。使用async关键字会生成一个状态机,其性能不如简单地返回任务。但是,在某些情况下,异常语义可能会令人惊讶。

以此代码为例:

Task FooAsync()
{
    return DoSomethingAsync("no await");
}

try-catch块是否会处理Hamster hamster = null; Func<Task> asyncAction = () => FooAsync(hamster.Name); var task = asyncAction(); try { await task; } catch { // handle }

它不会因为在调用NullReferenceException时同步抛出异常。但是,在这种情况下,处理异常,因为它在返回的任务中被捕获,并在等待该任务时重新抛出。

asyncAction

我个人使用返回任务的lambda来表示这一行表达式lambdas,因为它们通常非常简单。但是我的团队在经历了一些非常有害的错误之后,总是使用Func<Task> asyncAction = async () => await FooAsync(hamster.Name); async个关键字。

答案 1 :(得分:4)

这是这两种方法的IL Viewer的输出:

await Runner(() => DoSomethingAsync("no await"));

    .method private hidebysig instance class [mscorlib]System.Threading.Tasks.Task 
'<RunTheDemo>b__5_0'() cil managed
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() 
  = (01 00 00 00 )
.maxstack 8

// [42 32 - 42 60]
IL_0000: ldarg.0      // this
IL_0001: ldstr        "no await"
IL_0006: call         instance class [mscorlib]System.Threading.Tasks.Task TestClass::DoSomethingAsync(string)
IL_000b: ret
} // end of method CompanyManagementController::'<RunTheDemo>b__5_0'



await Runner(async () => await DoSomethingAsync("with await"));

.method private hidebysig instance class [mscorlib]System.Threading.Tasks.Task 
'<RunTheDemo>b__5_1'() cil managed
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.AsyncStateMachineAttribute::.ctor(class [mscorlib]System.Type) 
  = (
    01 00 45 57 65 62 43 61 72 64 2e 43 6f 6e 74 72 // ..TestClass
    6f 6c 6c 65 72 73 2e 43 6f 6d 70 61 6e 79 4d 61 // +<<RunTheDemo>
    6e 61 67 65 6d 65 6e 74 43 6f 6e 74 72 6f 6c 6c // b__5_1>d..
    65 72 2b 3c 3c 52 75 6e 54 68 65 44 65 6d 6f 3e  
    62 5f 5f 35 5f 31 3e 64 00 00                    
  )
  // MetadataClassType(TestClass+<<RunTheDemo>b__5_1>d)
.custom instance void [mscorlib]System.Diagnostics.DebuggerStepThroughAttribute::.ctor() 
  = (01 00 00 00 )
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() 
  = (01 00 00 00 )
.maxstack 2
.locals init (
  [0] class TestClass/'<<RunTheDemo>b__5_1>d' V_0,
  [1] valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder V_1
)

IL_0000: newobj       instance void TestClass/'<<RunTheDemo>b__5_1>d'::.ctor()
IL_0005: stloc.0      // V_0
IL_0006: ldloc.0      // V_0
IL_0007: ldarg.0      // this
IL_0008: stfld        class TestClass TestClass/'<<RunTheDemo>b__5_1>d'::'<>4__this'
IL_000d: ldloc.0      // V_0
IL_000e: call         valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::Create()
IL_0013: stfld        valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder TestClass/'<<RunTheDemo>b__5_1>d'::'<>t__builder'
IL_0018: ldloc.0      // V_0
IL_0019: ldc.i4.m1    
IL_001a: stfld        int32 TestClass/'<<RunTheDemo>b__5_1>d'::'<>1__state'
IL_001f: ldloc.0      // V_0
IL_0020: ldfld        valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder TestClass/'<<RunTheDemo>b__5_1>d'::'<>t__builder'
IL_0025: stloc.1      // V_1
IL_0026: ldloca.s     V_1
IL_0028: ldloca.s     V_0
IL_002a: call         instance void [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::Start<class TestClass/'<<RunTheDemo>b__5_1>d'>(!!0/*class TestClass/'<<RunTheDemo>b__5_1>d'*/&)
IL_002f: ldloc.0      // V_0
IL_0030: ldflda       valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder TestClass/'<<RunTheDemo>b__5_1>d'::'<>t__builder'
IL_0035: call         instance class [mscorlib]System.Threading.Tasks.Task [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::get_Task()
IL_003a: ret
} // end of method CompanyManagementController::'<RunTheDemo>b__5_1'

所以第二个使用异步状态机

答案 2 :(得分:3)

是的,它们是相同的,但这是一个相当简单的例子。这两者在功能上是等价的,你只是(可能,取决于编译器)在使用async时做更多的工作。

一个更好的案例,看看为什么async lambda是有用的,如果你需要处理一系列异步操作 - 那就是await的用途,毕竟:

await Runner(async () => await DoSomethingAsync(await httpClient.Get("www.google.com")));