2012-03-22 18 views
11

Sau khi đọc Eric Lippert’s answer Tôi có ấn tượng rằng awaitcall/cc là khá nhiều hai mặt của cùng một đồng tiền, với hầu hết các khác biệt cú pháp. Tuy nhiên, khi cố gắng thực sự thực hiện call/cc trong C# 5, tôi gặp phải một vấn đề: hoặc là tôi hiểu nhầm cuộc gọi/cc (có thể là khá), hoặc chờ đợi chỉ gợi nhớ của cuộc gọi/cc.C# await vs continuations: không hoàn toàn giống nhau?

Cân nhắc pseudo-code như thế này:

function main: 
    foo(); 
    print "Done" 

function foo: 
    var result = call/cc(bar); 
    print "Result: " + result; 

function bar(continuation): 
    print "Before" 
    continuation("stuff"); 
    print "After" 

Nếu sự hiểu biết của tôi về cuộc gọi/cc là đúng, thì đây nên in:

Before 
Result: stuff 
Done 

Quan trọng hơn, khi việc tiếp tục được gọi, chương trình trạng thái được khôi phục cùng với lịch sử cuộc gọi, để foo trả về thành main và không bao giờ quay lại bar.

Tuy nhiên, nếu được thực hiện bằng cách sử dụng await trong C#, hãy gọi việc tiếp tục không khôi phục lịch sử cuộc gọi này. foo trả về thành bar và không có cách nào (mà tôi có thể thấy) rằng await có thể được sử dụng để làm cho phần lịch sử cuộc gọi chính xác của việc tiếp tục.

Vui lòng giải thích: tôi đã hiểu sai hoàn toàn hoạt động của call/cc hoặc là await không hoàn toàn giống với call/cc?


Bây giờ tôi biết câu trả lời, tôi phải nói rằng có lý do chính đáng để nghĩ về chúng khá giống nhau. Hãy xem xét những gì các chương trình trên trông giống như trong pseudo-C# -5:

function main: 
    foo(); 
    print "Done" 

async function foo: 
    var result = await(bar); 
    print "Result: " + result; 

async function bar(): 
    print "Before" 
    return "stuff"; 
    print "After" 

Vì vậy, trong khi phong cách C# 5 không bao giờ cho chúng ta một đối tượng tiếp tục vượt qua một giá trị cho, tổng thể tương đồng là khá ấn tượng. Ngoại trừ rằng lần này hoàn toàn rõ ràng rằng "Sau" không bao giờ được gọi, không giống như trong ví dụ thực sự/cuộc gọi cc, đó là một lý do khác để yêu C# và khen ngợi thiết kế của nó!

+0

Timwi là chính xác; chờ đợi giống như một cuộc gọi "hiệu ứng cục bộ"/cc; đây là một sự tinh tế, tôi không nghĩ rằng để gọi ra trong câu trả lời ban đầu của tôi. Tôi đã cập nhật nó. –

Trả lời

18

await thực sự không hoàn toàn giống với call/cc.

Loại hoàn toàn cơ bản call/cc mà bạn đang nghĩ đến thực sự sẽ phải lưu và khôi phục toàn bộ ngăn xếp cuộc gọi. Nhưng await chỉ là một phép biến đổi thời gian biên dịch. Nó thực hiện tương tự nhưng không sử dụng ngăn xếp cuộc gọi thực.

Hãy tưởng tượng bạn có một chức năng async chứa một biểu thức đang chờ đợi:

async Task<int> GetInt() 
{ 
    var intermediate = await DoSomething(); 
    return calculation(intermediate); 
} 

Bây giờ tưởng tượng rằng các chức năng bạn gọi qua awaittự chứa một biểu await:

async Task<int> DoSomething() 
{ 
    var important = await DoSomethingImportant(); 
    return un(important); 
} 

Bây giờ nghĩ về những gì xảy ra khi kết thúc DoSomethingImportant() và kết quả của nó khả dụng. Kiểm soát trả về DoSomething(). Sau đó, DoSomething() kết thúc và điều gì sẽ xảy ra sau đó?Kiểm soát trả về GetInt(). Hành vi chính xác là nó sẽ là nếu GetInt() có trong ngăn xếp cuộc gọi. Nhưng nó không thực sự; bạn phải sử dụng số await tại mỗi cuộc gọi mà bạn muốn mô phỏng theo cách này. Vì vậy, ngăn xếp cuộc gọi được nâng lên thành một ngăn xếp meta-call được thực hiện trong awaiter.

Cùng, tình cờ, đúng đối yield return:

IEnumerable<int> GetInts() 
{ 
    foreach (var str in GetStrings()) 
     yield return computation(str); 
} 

IEnumerable<string> GetStrings() 
{ 
    foreach (var stuff in GetStuffs()) 
     yield return computation(stuff); 
} 

Bây giờ nếu tôi gọi GetInts(), những gì tôi nhận được lại là một đối tượng mà gói gọn tình trạng thực hiện của GetInts() (vì vậy mà gọi MoveNext() vào nó sơ yếu lý lịch hoạt động nơi nó rời đi). Bản thân đối tượng này chứa trình lặp đang lặp qua GetStrings() và gọi MoveNext() trên rằng. Do đó, ngăn xếp cuộc gọi thực được thay thế bằng một hệ thống phân cấp các đối tượng tạo lại ngăn xếp cuộc gọi chính xác mỗi lần thông qua một loạt các cuộc gọi đến MoveNext() trên đối tượng bên trong tiếp theo.

+0

Ngẫu nhiên khi bạn gỡ lỗi một async bạn thấy MoveNext là tên phương thức trên điểm ngắt. Điều đó cho thấy họ đã sử dụng trình tạo máy trạng thái máy lặp từ IEnumerable. –

Các vấn đề liên quan