2015-08-03 26 views
7

Tôi muốn sử dụng TaskCompletionSource quấn MyService mà là một dịch vụ đơn giản:TaskCompletionSource ném "Một cố gắng đã được thực hiện để chuyển một nhiệm vụ đến một trạng thái cuối cùng khi nó đã hoàn thành"

public static Task<string> ProcessAsync(MyService service, int parameter) 
{ 
    var tcs = new TaskCompletionSource<string>(); 
    //Every time ProccessAsync is called this assigns to Completed! 
    service.Completed += (sender, e)=>{ tcs.SetResult(e.Result); }; 
    service.RunAsync(parameter); 
    return tcs.Task; 
} 

Mã này được hoạt động hiệu quả lần đầu tiên. Nhưng giây thứ hai thời gian tôi gọi ProcessAsync chỉ đơn giản là xử lý sự kiện cho các Completed được gán lại (cùng một biến service được sử dụng mọi lúc) và do đó nó sẽ thực hiện hai lần! và lần thứ hai nó ném ngoại lệ này:

nỗ lực nhiệm vụ chuyển trạng thái cuối cùng khi đã hoàn thành

Tôi không chắc chắn, tôi nên khai báo tcs như là một biến mức lớp như thế này:

TaskCompletionSource<string> tcs; 

public static Task<string> ProccessAsync(MyService service, int parameter) 
{ 
    tcs = new TaskCompletionSource<string>(); 
    service.Completed -= completedHandler; 
    service.Completed += completedHandler; 
    return tcs.Task;  
} 

private void completedHandler(object sender, CustomEventArg e) 
{ 
    tcs.SetResult(e.Result); 
} 

Tôi phải bọc nhiều phương thức với các kiểu trả về khác nhau và cách này tôi phải viết mã, biến, trình xử lý sự kiện để không chắc đây có phải là cách hay nhất trong kịch bản này không. Vậy có cách nào tốt hơn để thực hiện công việc này không?

Trả lời

13

Vấn đề ở đây là sự kiện Completed được nêu lên trên mỗi hành động nhưng chỉ có thể hoàn thành TaskCompletionSource một lần.

Bạn vẫn có thể sử dụng địa chỉ TaskCompletionSource (và bạn nên). Bạn chỉ cần hủy đăng ký cuộc gọi lại trước khi hoàn thành TaskCompletionSource. Bằng cách đó gọi lại cụ thể này với cụ TaskCompletionSource này sẽ không bao giờ được gọi là một lần nữa:

public static Task<string> ProcessAsync(MyService service, int parameter) 
{ 
    var tcs = new TaskCompletionSource<string>(); 
    EventHandler<CustomEventArg> callback = null; 
    callback = (sender, e) => 
    { 
     service.Completed -= callback; 
     tcs.SetResult(e.Result); 
    }; 
    service.Completed += callback; 
    service.RunAsync(parameter); 
    return tcs.Task; 
} 

này cũng sẽ giải quyết được rò rỉ bộ nhớ càng tốt mà bạn có khi dịch vụ của bạn giữ tham chiếu đến tất cả các đại biểu.

Bạn nên nhớ rằng bạn không thể có nhiều hoạt động trong số này hoạt động đồng thời. Ít nhất là không trừ khi bạn có một cách để phù hợp với yêu cầu và phản hồi.

+0

@HosseinNarimaniRad Không nhất thiết. Bạn vẫn có thể sử dụng TCS cục bộ với biểu thức lambda, mặc dù nó không đơn giản như vậy. Nhìn vào mã trong câu trả lời của tôi. – i3arnon

+0

ý tưởng hay. Tôi sẽ thử. –

+0

Nó nói không thể chuyển đổi hoàn toàn Hành động <...,...> thành EventHandler <...> tại 'service.Completed- = callback;' do đó tôi sửa lỗi bằng cách thay đổi loại. –

1

Dường như MyService sẽ tăng sự kiện Completed nhiều lần. điều này gây ra SetResult để được gọi nhiều hơn một lần gây ra lỗi của bạn.

Bạn có 3 tùy chọn mà tôi thấy. Thay đổi sự kiện đã hoàn thành để chỉ được nâng lên một lần (Có vẻ lẻ mà bạn có thể hoàn thành nhiều lần), thay đổi SetResult thành TrySetResult để nó không ném ngoại lệ khi bạn cố gắng đặt nó lần thứ hai (điều này giới thiệu rò rỉ bộ nhớ nhỏ như sự kiện này vẫn được gọi và nguồn hoàn vẫn cố gắng thể được thiết lập), hoặc hủy đăng ký từ sự kiện (i3arnon's answer)

+0

Có, tôi thích tùy chọn thứ 3. Nhưng thành thật mà nói, tôi không thể tìm ra cách gọi 'callback' vừa mới được khớp như thế nào trước đó và sử dụng' service.Completed - = callback' nó sẽ bị xóa. Tôi nghĩ mỗi khi chúng ta tạo một 'callback' mới, nó không đề cập đến cùng một trình xử lý vì vậy tôi nghĩ rằng tôi phải khai báo trình xử lý trong phần thân của lớp. –

+1

Nó là một biến chụp, nó là cùng một "vấn đề" mà bạn nhận được khi bạn sử dụng 'i' [từ một vòng lặp' for'] (http://stackoverflow.com/questions/8116709/odd-lambda-behavior) bên trong một lambda. Biến 'callback' bên trong lambda là cùng một biến để nó có thể được sử dụng bên trong lambda mặc dù nó chưa được khởi tạo. Bạn đúng là nó tạo ra một cuộc gọi lại mỗi lần, nhưng bạn đang giữ một tham chiếu đến gọi lại ẩn danh để bạn có thể hủy đăng ký sau đó (và "thời điểm sau" chỉ xảy ra bên trong chính cuộc gọi lại). –

+0

Bạn có nghĩa là tham chiếu đến gọi lại ẩn danh này giống nhau đối với mọi lần thực thi 'ProcessAsync' không? –

3

Một giải pháp thay thế cho i3arnon 's answer sẽ là:

public async static Task<string> ProcessAsync(MyService service, int parameter) 
{ 
    var tcs = new TaskCompletionSource<string>(); 

    EventHandler<CustomEventArg> callback = 
     (s, e) => tcs.SetResult(e.Result); 

    try 
    { 
     contacts.Completed += callback; 

     contacts.RunAsync(parameter); 

     return await tcs.Task; 
    } 
    finally 
    { 
     contacts.Completed -= callback; 
    } 
} 

Tuy nhiên, giải pháp này sẽ có một trình biên dịch tạo ra máy trạng thái. Nó sẽ sử dụng nhiều bộ nhớ và CPU hơn.

+0

Tốt, ý tưởng mới được chào đón –

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