2010-05-06 42 views
12

Gần đây tôi thấy mình cần một cơ chế "lửa và quên" an toàn để chạy mã không đồng bộ.Cuộc gọi ủy nhiệm không đồng bộ về lửa và không đồng bộ trong C#

Lý tưởng nhất, những gì tôi muốn làm là một cái gì đó như:

var myAction = (Action)(() => Console.WriteLine("yada yada")); 
myAction.FireAndForget(); // async invocation 

Thật không may, sự lựa chọn rõ ràng gọi BeginInvoke() mà không có một EndInvoke() tương ứng không hoạt động - nó kết quả trong một sự rò rỉ tài nguyên chậm (kể từ khi asyn nhà nước được tổ chức bởi thời gian chạy và không bao giờ phát hành ... nó mong đợi một cuộc gọi cuối cùng để EndInvoke().Tôi cũng không thể chạy mã trên hồ bơi NET thread. Bởi vì nó có thể mất một thời gian rất dài để hoàn thành (nó chỉ nên để chạy mã tương đối ngắn ngủi trên nhóm luồng) - điều này làm cho nó không thể sử dụng ThreadPool.QueueUserWorkItem().

Ban đầu, tôi chỉ cần hành vi này cho các phương thức có chữ ký khớp với Action, Action<...> hoặc Func<...>. Vì vậy, tôi đặt cùng một tập hợp các phương pháp mở rộng (xem danh sách bên dưới) cho phép tôi thực hiện điều này mà không cần phải chạy vào tài nguyên bị rò rỉ. Có quá tải cho mỗi phiên bản Hành động/Func.

Thật không may, bây giờ tôi muốn chuyển mã này sang .NET 4, nơi số lượng các tham số chung về Hành động và Func đã được tăng lên đáng kể. Trước khi tôi viết một kịch bản T4 để tạo ra chúng, tôi cũng hy vọng tìm ra một cách đơn giản hơn để làm điều này. Bất kỳ ý tưởng được chào đón.

public static class AsyncExt 
{ 
    public static void FireAndForget(this Action action) 
    { 
     action.BeginInvoke(OnActionCompleted, action); 
    } 

    public static void FireAndForget<T1>(this Action<T1> action, T1 arg1) 
    { 
     action.BeginInvoke(arg1, OnActionCompleted<T1>, action); 
    } 

    public static void FireAndForget<T1,T2>(this Action<T1,T2> action, T1 arg1, T2 arg2) 
    { 
     action.BeginInvoke(arg1, arg2, OnActionCompleted<T1, T2>, action); 
    } 

    public static void FireAndForget<TResult>(this Func<TResult> func, TResult arg1) 
    { 
     func.BeginInvoke(OnFuncCompleted<TResult>, func); 
    } 

    public static void FireAndForget<T1,TResult>(this Func<T1, TResult> action, T1 arg1) 
    { 
     action.BeginInvoke(arg1, OnFuncCompleted<T1,TResult>, action); 
    } 

    // more overloads of FireAndForget<..>() for Action<..> and Func<..> 

    private static void OnActionCompleted(IAsyncResult result) 
    { 
     var action = (Action)result.AsyncState; 
     action.EndInvoke(result); 
    } 

    private static void OnActionCompleted<T1>(IAsyncResult result) 
    { 
     var action = (Action<T1>)result.AsyncState; 
     action.EndInvoke(result); 
    } 

    private static void OnActionCompleted<T1,T2>(IAsyncResult result) 
    { 
     var action = (Action<T1,T2>)result.AsyncState; 
     action.EndInvoke(result); 
    } 

    private static void OnFuncCompleted<TResult>(IAsyncResult result) 
    { 
     var func = (Func<TResult>)result.AsyncState; 
     func.EndInvoke(result); 
    } 

    private static void OnFuncCompleted<T1,TResult>(IAsyncResult result) 
    { 
     var func = (Func<T1, TResult>)result.AsyncState; 
     func.EndInvoke(result); 
    } 

    // more overloads of OnActionCompleted<> and OnFuncCompleted<> 

} 
+0

mát câu hỏi. Mã đau! Điều duy nhất họ có điểm chung là họ đều là MulticastDelegate. Tôi không chắc chắn sẽ có một cách ăn kiêng mã không an toàn. Làm thế nào để MS tạo ra tất cả nó ở nơi đầu tiên? – spender

+1

@spender: Trình biên dịch sẽ tự động tạo ra các phương thức 'BeginInvoke' /' EndInvoke' trên mọi loại đại biểu được gõ mạnh mẽ vào các đối số của đại biểu. – LBushkin

+0

Để chắc chắn. Dường như với tôi để khuyến khích mã boilerplate đáng kể, và tôi ngạc nhiên rằng .net4 đã không giới thiệu một cách chung chung hơn (hoặc siêu chung) tuyên bố các loại cấu trúc ... mặc dù tôi đồng ý rằng trong hầu hết các sane mã, danh sách tham số sẽ không vượt quá 5 hoặc lâu hơn. – spender

Trả lời

7

Bạn có thể vượt qua EndInvoke như AsyncCallback cho BeginInvoke:

Action<byte[], int, int> action = // ... 

action.BeginInvoke(buffer, 0, buffer.Length, action.EndInvoke, null); 

Điều đó giúp đỡ?

+0

Đây là điều tôi đã xem xét ... mối quan tâm từ các nhà phát triển khác mà tôi làm việc với nó là sẽ dễ dàng quên đi trong 'action.EndInvoke' - với kết quả là sẽ có rò rỉ thầm lặng bên trong mã. Chúng tôi thậm chí đã xem xét việc viết một quy tắc FxCop tùy chỉnh cho việc này, nhưng quá lâu và có nguy cơ mã vẫn có thể đưa nó vào sản xuất. Tôi, tuy nhiên, xem xét lại giá trị của lựa chọn này một lần nữa. – LBushkin

+0

Ít nhất bạn có thể đơn giản hóa việc triển khai FireAndForget một chút. – dtb

+0

Phiên bản thực sự của mã thực sự có logic hơn một chút trong OnAction/OnFunc đã hoàn thành các phương pháp mà tôi không muốn lộn xộn vào câu hỏi (chủ yếu là một số đăng nhập mất bao lâu để hoàn thành cuộc gọi không đồng bộ) - nhưng, về nguyên tắc, vâng. – LBushkin

0

Đó là thông minh chap Skeet tiếp cận chủ đề này here.

Có một cách tiếp cận khác để "đốt cháy và quên" khoảng một nửa xuống.

+0

Vâng, tôi đã thấy cách tiếp cận đó trước đây. Vấn đề chính là nó mất an toàn kiểu (và do đó hỗ trợ intellisense và refactoring) - nhưng nó hỗ trợ nhiều chữ ký đại biểu hơn. – LBushkin

3

Phương pháp BeginInvoke do trình biên dịch tạo cũng được gọi trên nhóm chủ đề (reference). Vì vậy, tôi nghĩ ThreadPool.QueueUserWorkItem sẽ ổn thôi, ngoại trừ việc bạn đang rõ ràng hơn một chút về nó, tôi đoán (và tôi cho rằng một CLR trong tương lai có thể chọn chạy các phương thức ed BeginInvoke 'trên hồ bơi chủ đề khác nhau).

4

Làm thế nào về một cái gì đó như:

public static class FireAndForgetMethods 
{ 
    public static void FireAndForget<T>(this Action<T> act,T arg1) 
    { 
     var tsk = Task.Factory.StartNew(()=> act(arg1), 
             TaskCreationOptions.LongRunning); 
    } 
} 

sử dụng nó như:

Action<int> foo = (t) => { Thread.Sleep(t); }; 
foo.FireAndForget(100); 

Để thêm loại an toàn, chỉ cần mở rộng ra các phương pháp helper. T4 có lẽ là tốt nhất ở đây.

+0

Trông rất thú vị! –

+0

ContinueWith (...) trả về một đối tượng Task thứ hai, vì vậy bạn vẫn còn lại với một Task không bị xử lý. Trong thực tế thực tế không cần phải vứt bỏ đối tượng Task ban đầu trong trường hợp này vì việc xử lý chỉ cần thiết nếu Task được yêu cầu thực hiện chờ đợi chặn. Xem [câu hỏi này] (http://stackoverflow.com/questions/3734280/) –

+0

điểm tốt - sẽ sửa –

4

tôi nhận thấy không ai trả lời này:

Tôi cũng không thể chạy mã trên hồ bơi .NET chủ đề bởi vì nó có thể mất một thời gian rất dài để hoàn thành (nó nên chỉ chạy tương đối ngắn sống mã trên hồ bơi thread) - điều này làm cho nó không thể sử dụng ThreadPool.QueueUserWorkItem().

Tôi không chắc chắn nếu bạn đang nhận thức được điều này, nhưng các đại biểu async thực sự thực hiện chính xác này - họ hàng đợi công việc trên một sợi công nhân trong ThreadPool, chính xác giống như nếu bạn đã làm QueueUserWorkItem.

Thời gian duy nhất khi đại biểu async hoạt động khác nhau là khi họ là đại biểu khung đặc biệt như Stream.BeginRead hoặc Socket.BeginSend. Chúng sử dụng các cổng hoàn thành I/O thay thế.

Trừ khi bạn đang quay hàng trăm tác vụ này trong môi trường ASP.NET, tôi khuyên bạn chỉ nên sử dụng nhóm luồng.

ThreadPool.QueueUserWorkItem(s => action()); 

Hoặc, trong .NET 4, bạn có thể sử dụng các nhà máy công việc:

Task.Factory.StartNew(action); 

(Lưu ý rằng ở trên cũng sẽ sử dụng hồ bơi thread!)

+0

Thật vậy, đây là một vấn đề. Tôi sẽ phải kiểm tra lại cách tiếp cận cho thực tế này. Có bất kỳ tài liệu rõ ràng về MSDN, nơi này được nêu? – LBushkin

+0

@LBushkin: Bạn biết đấy, đó là một câu hỏi rất hay, và tôi không nghĩ rằng có * là bất kỳ tài liệu rõ ràng nào, nó được giả định là một chi tiết thực hiện có thể. Tôi phát hiện ra một số khi tôi đã làm một vài thí nghiệm cho câu hỏi của người khác trên ASP.NET ThreadPool đói; Tôi bước vào một cuộc gọi lại không đồng bộ và thấy rằng nó đã thực sự lấy một sợi công nhân trong hồ bơi. Nếu bạn tìm thấy bất kỳ tài liệu nào, hãy cho tôi biết, thật tuyệt khi có liên kết đến. – Aaronaught

+0

Vâng, tôi đã cố gắng tìm một tham chiếu cho điều này và vẽ một ô trống. – spender

0

Bạn có thể viết riêng của bạn triển khai luồng. Có lẽ nó nghe giống như công việc mặc hơn nó thực sự là như vậy. Sau đó, bạn không phải tuân thủ "chỉ chạy mã tương đối ngắn".

0

Đưa phương pháp mở rộng này một shot (mỗi C# Is action.BeginInvoke(action.EndInvoke,null) a good idea?) để đảm bảo không có rò rỉ bộ nhớ:

public static void FireAndForget(this Action action) 
{ 
    action.BeginInvoke(action.EndInvoke, null); 
} 

Và bạn có thể sử dụng nó với các thông số chung chung như:

T1 param1 = someValue; 
T2 param2 = otherValue; 
(() => myFunc<T1,T2>(param1,param2)).FireAndForget(); 
Các vấn đề liên quan