2010-11-04 24 views
9

Đây là mã:IAsyncResult.AsyncWaitHandle.WaitOne() hoàn thành trước callback

class LongOp 
{ 
    //The delegate 
    Action longOpDelegate = LongOp.DoLongOp; 
    //The result 
    string longOpResult = null; 

    //The Main Method 
    public string CallLongOp() 
    { 
     //Call the asynchronous operation 
     IAsyncResult result = longOpDelegate.BeginInvoke(Callback, null); 

     //Wait for it to complete 
     result.AsyncWaitHandle.WaitOne(); 

     //return result saved in Callback 
     return longOpResult; 
    } 

    //The long operation 
    static void DoLongOp() 
    { 
     Thread.Sleep(5000); 
    } 

    //The Callback 
    void Callback(IAsyncResult result) 
    { 
     longOpResult = "Completed"; 
     this.longOpDelegate.EndInvoke(result); 
    } 
} 

Đây là trường hợp thử nghiệm:

[TestMethod] 
public void TestBeginInvoke() 
{ 
    var longOp = new LongOp(); 
    var result = longOp.CallLongOp(); 

    //This can fail 
    Assert.IsNotNull(result); 
} 

Nếu đây là chạy test có thể thất bại. Tại sao chính xác?

Có rất ít tài liệu về cách delegate.BeginInvoke hoạt động. Có ai có bất kỳ thông tin chi tiết nào mà họ muốn chia sẻ không?

Cập nhật Đây là trường hợp điều kiện tinh tế không được ghi chép rõ trong MSDN hoặc ở nơi khác. Vấn đề, như được giải thích trong câu trả lời được chấp nhận, đó là khi hoạt động hoàn thành Wait Handle được báo hiệu, và sau đó Callback được thực thi. Các tín hiệu phát hành các chủ đề chính chờ đợi và bây giờ thực hiện gọi lại vào "cuộc đua". Jeffry Richter's suggested implementation cho biết điều gì đang xảy ra phía sau hậu trường:

// If the event exists, set it 
    if (m_AsyncWaitHandle != null) m_AsyncWaitHandle.Set(); 

    // If a callback method was set, call it 
    if (m_AsyncCallback != null) m_AsyncCallback(this); 

Để biết giải pháp, hãy tham khảo câu trả lời của Ben Voigt. Việc triển khai đó không phải chịu thêm chi phí của một lần chờ xử lý thứ hai.

+0

Xóa cuộc gọi lại và thử lại. – jgauffin

+0

@jgauffin, nếu bạn nhận thấy câu hỏi không hỏi "Làm cách nào để làm việc này?" Rõ ràng đây là một ví dụ giả tạo. –

+0

Câu hỏi của bạn là: "Nếu điều này xảy ra, trường hợp thử nghiệm có thể không thành công. Tại sao lại chính xác?". Tôi * đã * trả lời điều đó. Bởi vì bạn cố gắng kết hợp hai cách khác nhau để xử lý một hoạt động không đồng bộ. – jgauffin

Trả lời

8

ASyncWaitHandle.WaitOne() được báo hiệu khi hoạt động không đồng bộ hoàn tất. Đồng thời CallBack() được gọi.

Điều này có nghĩa là mã sau khi WaitOne() được chạy trong chuỗi chính và CallBack được chạy trong một chuỗi khác (có thể giống như chạy DoLongOp()). Điều này dẫn đến một điều kiện chủng tộc mà giá trị của longOpResult về cơ bản là không rõ tại thời điểm nó được trả về.

Người ta có thể đã dự kiến ​​rằng ASyncWaitHandle.WaitOne() sẽ được báo hiệu khi CallBack đã kết thúc, nhưng đó chỉ là không phải là cách nó hoạt động ;-)

Bạn sẽ cần ManualResetEvent khác để có các chủ đề chính chờ cho CallBack để thiết lập longOpResult.

0

Cuộc gọi lại được thực hiện sau phương thức CallLongOp. Vì bạn chỉ đặt giá trị biến trong hàm gọi lại, nên nó là lý do khiến nó không có giá trị. Đọc nội dung này: link text

+0

Đó là để nói, kết quả mà bạn đang tìm kiếm chưa được thiết lập như là gọi lại si không được gọi cho đến sau khi phương thức CallLongOp trả về. – Kell

+0

cảm ơn phản ứng của bạn. Cuộc gọi lại không phải lúc nào cũng được thực hiện sau phương thức CallLongOp. Thử đặt Thread.Sleep (500); trong CallLongOp trước khi trả về longOpResult; và bài kiểm tra sẽ trôi qua. –

3

gì đang xảy ra

Kể từ khi hoạt động của bạn DoLongOp đã hoàn thành, hồ sơ kiểm soát trong vòng CallLongOp và chức năng hoàn thành trước khi phẫu thuật Callback đã hoàn thành. Assert.IsNotNull(result); sau đó thực hiện trước longOpResult = "Completed";.

Tại sao? AsyncWaitHandle.WaitOne() sẽ chỉ chờ hoạt động không đồng bộ của bạn hoàn tất, chứ không phải của bạn Gọi lại

Tham số gọi lại của BeginInvoke thực sự là AsyncCallback delegate, có nghĩa là gọi lại của bạn được gọi không đồng bộ. Điều này là do thiết kế, vì mục đích là xử lý các kết quả hoạt động không đồng bộ (và là toàn bộ mục đích của tham số gọi lại này).

Vì hàm BeginInvoke thực sự gọi hàm gọi lại của bạn, cuộc gọi IAsyncResult.WaitOne chỉ dành cho thao tác và không ảnh hưởng đến cuộc gọi lại.

Xem Microsoft documentation (phần Thực hiện phương thức gọi lại khi cuộc gọi không đồng bộ hoàn thành). Ngoài ra còn có một lời giải thích và ví dụ tốt.

Nếu chủ đề bắt đầu cuộc gọi không đồng bộ không cần phải là luồng xử lý kết quả, bạn có thể thực hiện một phương thức gọi lại khi cuộc gọi kết thúc. Phương thức gọi lại được thực hiện trên luồng ThreadPool.

Giải pháp

Nếu bạn muốn đợi cho cả hai hoạt động và gọi lại, bạn cần phải xử lý các tín hiệu chính mình. Một ManualReset là một cách để thực hiện điều đó chắc chắn mang lại cho bạn quyền kiểm soát nhiều nhất (và đó là cách Microsoft đã thực hiện nó trong tài liệu của họ).

Đây là mã được sửa đổi bằng ManualResetEvent.

public class LongOp 
{ 
    //The delegate 
    Action longOpDelegate = LongOp.DoLongOp; 
    //The result 
    public string longOpResult = null; 

    // Declare a manual reset at module level so it can be 
    // handled from both your callback and your called method 
    ManualResetEvent waiter; 

    //The Main Method 
    public string CallLongOp() 
    { 
     // Set a manual reset which you can reset within your callback 
     waiter = new ManualResetEvent(false); 

     //Call the asynchronous operation 
     IAsyncResult result = longOpDelegate.BeginInvoke(Callback, null);  

     // Wait 
     waiter.WaitOne(); 

     //return result saved in Callback 
     return longOpResult; 
    } 

    //The long operation 
    static void DoLongOp() 
    { 
     Thread.Sleep(5000); 
    } 

    //The Callback 
    void Callback(IAsyncResult result) 
    { 
     longOpResult = "Completed"; 
     this.longOpDelegate.EndInvoke(result); 

     waiter.Set(); 
    } 
} 

Đối với ví dụ bạn đã cho, bạn sẽ tốt hơn không sử dụng một callback và thay vào đó xử lý các kết quả trong chức năng CallLongOp của bạn, trong trường hợp này WaitOne của bạn trên các đại biểu hoạt động sẽ hoạt động tốt.

+0

cảm ơn phản hồi. Vậy chính xác chúng ta làm gì với IAsyncResult mà chúng ta nhận được từ BeginInvoke()? –

+0

Bạn có thể sử dụng nó để tạm dừng thực hiện trong phương thức được gọi là begininvoke. I E. Bất kỳ kịch bản nào mà bạn muốn đợi bản thân hoạt động hoàn thành. – badbod99

+0

ĐIỀU KIỆN RACE! Bạn nên tạo sự kiện trước khi gọi 'BeginInvoke', nhưng việc thêm nhiều đối tượng đồng bộ hóa là không cần thiết và không hiệu quả. –

5

Như những người khác đã nói, result.WaitOne chỉ có nghĩa là mục tiêu của BeginInvoke đã kết thúc và không phải gọi lại. Vì vậy, chỉ cần đặt mã sau xử lý vào đại biểu BeginInvoke.

//Call the asynchronous operation 
    Action callAndProcess = delegate { longOpDelegate(); Callafter(); }; 
    IAsyncResult result = callAndProcess.BeginInvoke(r => callAndProcess.EndInvoke(r), null); 


    //Wait for it to complete 
    result.AsyncWaitHandle.WaitOne(); 

    //return result saved in Callafter 
    return longOpResult; 
+0

Ok ... giải pháp rất tốt đẹp thực sự! Nhưng để giải thích lý do tại sao tôi nghĩ rằng tôi đã bảo hiểm khá tốt. – badbod99

+0

Thông minh nhưng khi nào điều này thực sự hữu ích? ManualReset cho phép bạn kiểm soát để chờ đợi bất cứ khi nào bạn muốn đợi, thao tác này sẽ gọi hoạt động sau đó gọi lại để xử lý kết quả và đợi cả hai cùng một lúc. Bạn chỉ có thể xử lý các kết quả trong hoạt động chính nó nếu đó là những gì bạn muốn. – badbod99

+0

@ badbod99: Điều này cho phép bạn xử lý kết quả ngay cả khi bạn không viết hàm được nhồi vào 'longOpDelegate' (hoặc đó là phương thức của lớp khác và không có quyền truy cập thành viên 'longOpResult' riêng tư hoặc bạn không muốn giới thiệu khớp nối ngược, hoặc ...). –

0

Tôi đã gặp vấn đề tương tự gần đây và tôi đã tìm ra cách khác để giải quyết vấn đề, nó hoạt động trong trường hợp của tôi. Về mặt khái quát nếu hết thời gian chờ, bạn hãy kiểm tra lại cờ IsCompleted khi Wait Handle đang hết thời gian chờ. Trong trường hợp của tôi, xử lý chờ đợi được báo hiệu trước khi chặn luồng và ngay sau điều kiện if, vì vậy hãy kiểm tra lại sau khi hết thời gian chờ.

while (!AsyncResult.IsCompleted) 
{ 
    if (AsyncWaitHandle.WaitOne(10000)) 
     break; 
} 
Các vấn đề liên quan