2011-11-30 30 views
2

我正在設計一個API,公開一個可取消的Task,並且想確保我已經正確地設計了它。是否有暴露Task s的標準模式(可能類似於APM BeginXxx/EndXxx模式)?任何改善建議?請參閱MyAPI.Run公開一個可取消的任務

是否Test2演示了並行運行多個MyAPI.Run任務的最佳方式?

public static class MyAPI 
{ 
    public static Task<MyResult> Run(CancellationToken token) { 
     // lazily create Task, so as to include the specified CancellationToken 
     return new Task<MyResult>(MyPrivateAsyncMethod, token, token); 
    } 

    private static MyResult MyPrivateAsyncMethod(object state) { 
     CancellationToken ct = (CancellationToken)state; 
     ct.ThrowIfCancellationRequested(); 
     return new MyResult(); 
    } 
} 

public static class TestMyAPI 
{ 
    // User can start the Task directly 
    public static void Test1() { 
     CancellationTokenSource cts = new CancellationTokenSource(); 
     MyAPI.Run(cts.Token) 
      .ContinueWith(task => Console.WriteLine(task.Result.ToString())) 
      .Start(); 
    } 

    // User must wrap in new Tasks to get Parent/Child relationship 
    public static void Test2() { 
     CancellationTokenSource cts = new CancellationTokenSource(); 
     Task.Factory.StartNew(() => 
     { 
      var childTasks = new[] { 
       Task.Factory.StartNew<MyResult>(() => MyAPI.Run(cts.Token).Result, cts.Token, TaskCreationOptions.AttachedToParent, TaskScheduler.Default), 
       Task.Factory.StartNew<MyResult>(() => MyAPI.Run(cts.Token).Result, cts.Token, TaskCreationOptions.AttachedToParent, TaskScheduler.Default) 
      }; 

      Task.Factory 
       .ContinueWhenAll<MyResult>(childTasks, tasks => { foreach(var task in tasks) task.ToString(); }) 
       .Start(); 
     }, cts.Token); 
    } 
} 

回答

2

有一對夫婦在你的執行問題,我會考慮改變。

首先,這種方法:

public static Task<MyResult> Run(CancellationToken token) { 
    // lazily create Task, so as to include the specified CancellationToken 
    return new Task<MyResult>(MyPrivateAsyncMethod, token, token); 
} 

這是危險的 - 原料藥返回TaskTask<T>是返回一個「熱」的任務約定。這意味着您應該設計您的API以始終返回已運行的任務

這是爲了防止出現問題,就像你在上面的代碼中有一個:

MyAPI.Run(cts.Token) 
     .ContinueWith(task => Console.WriteLine(task.Result.ToString())) 
     .Start(); 

如果你看看這個,它可以被改寫爲:

Task<MyResult> original = MyAPI.Run(cts.Token); 
    Task second = original.ContinueWith(task => Console.WriteLine(task.Result.ToString())); 
    second.Start(); 

在這裏,你」實際上調用啓動的是錯誤的任務 - 不是原來的任務,而是延續...一個典型的方法會使「運行」返回一個熱門任務,這將允許你寫這個:

MyAPI.Run(cts.Token) 
     .ContinueWith(task => Console.WriteLine(task.Result.ToString())); 

再一次,這在你的第二個例子中也是如此。你可以Task.Factory.StartNew,並看看MyAPI.Run,但從來沒有實際上呼籲Start內的任務...因此,這些任務永遠不會開始,永遠不會完成。第二,我會建議使用.NET 4.5中出現的新命名規則,僅僅爲了將來打樣。這是爲了使返回TaskTask<T>的例程具有Async後綴的名稱。在這種情況下,它將是MyAPI.RunAsync。這將允許你的代碼看起來像在下一個.NET版本中出現的框架代碼。

最後,關於你的最後一個問題:

不Test2的演示並行運行多個任務MyAPI.Run最好的方式?

其實,如果你讓Run方法返回一個正在運行的任務,這將成爲簡單得多:

public static void Test2() { 
    CancellationTokenSource cts = new CancellationTokenSource(); 
    var task1 = MyAPI.RunAsync(cts.Token); 
    var task2 = MyAPI.RunAsync(cts.Token); 

    Task.Factory.ContinueWhenAll<MyResult>(
     new[] { task1, task2 }, 
     tasks => { 
      foreach(var task in tasks) task.ToString(); 
     }, 
     cts.Token); 
} 
+0

關於你的Test2重寫,會發生什麼異常處理?具體來說,通過父母/孩子的安排,我會'嘗試'父母並且抓住子女拋出的任何東西的'AggergateException'。這將如何工作在你的重寫? – nmarler

+0

@nmarler你必須在繼續處理它 - 你可以明確地將一個Try/catch包圍在一個調用中,以便繼續處理錯誤,如果需要的話。 –

+0

我正在考慮只返回'熱'任務的概念。在我的應用程序中,我有一個「SequenceManager」對象,負責運行其他類中的一些任務。當SequenceManager被初始化時,我的意圖是以某種安排(某些順序相互連接,某些異步)組成任務子對象任務。然後,在將來的某個時刻,可以運行這個「主」任務。我認爲現在應該簡化SequenceManager init,而是暴露一個RunAsync方法,它將即時組成子RunAsyncs。謝謝,你的建議是非常有幫助的 – nmarler