2015-09-10 40 views
3

我有一個項目,以非常相似的方式執行幾個操作(訂閱完成事件,執行任務,退出完成事件,還處理取消,超時等),所以我決定編寫處理該執行的實用程序類。但是,我遇到了一個我不明白的情況,因此我不知道如何解決。如何處理異常與明確構建的任務

這過於簡單的代碼說明了這個問題:

class Program 
{ 
    static void Main(string[] args) 
    { 
     Do(); 
     Console.Read(); 
    } 

    private static async Task Do() 
    { 
     var task = new Task(async() => await Operation()/*this throws and terminates the application*/); 

     try 
     { 
      await OperationExecuter.ExecuteAsync(task); 
     } 
     catch (InvalidOperationException) 
     { 
      //I expected the exception to be caught here 
     } 
    } 


    static async Task Operation() 
    { 
     await Task.Delay(1000); 
     throw new InvalidOperationException(); 
    } 
} 

class OperationExecuter 
{ 
    public static async Task ExecuteAsync(Task task) 
    { 
     task.Start(); 
     await task; //I expected the exception to be unwrapped and thrown here 
    } 
} 

我也試過有像var task = new Task(() => Operation());任務,但異常從未處理(雖然它不終止應用程序,因爲它不是在上升主線程)。

如何正確處理異常?

更改實施採取行動的收益率相同的結果:

class Program 
{ 
    static void Main(string[] args) 
    { 
     Do(); 
     Console.Read(); 
    } 

    private static async Task Do() 
    { 
     var action = new Action(async() => await Operation() /*this throws and terminates the application*/); 

     try 
     { 
      await OperationExecuter.ExecuteAsync(action); 
     } 
     catch (InvalidOperationException) 
     { 
      //I expected the exception to be caught here 
     } 
    } 


    static async Task Operation() 
    { 
     await Task.Delay(1000); 
     throw new InvalidOperationException(); 
    } 
} 

class OperationExecuter 
{ 
    public static async Task ExecuteAsync(Action action) 
    { 
     await Task.Run(action); //I expected the exception to be unwrapped and thrown here 
    } 
} 

對於好奇的人更現實的OperationExecuter將沿着線的東西:

class Program 
{ 
    static void Main(string[] args) 
    { 
     Do(); 
     Do2(); 
     Console.Read(); 
    } 

    private static async Task Do() 
    { 
     var service = new Service(new Hardware()); 
     try 
     { 
      await 
       OperationExecuter.ExecuteAsync(service, handler => service.Operation1Completed += handler, 
        handler => service.Operation1Completed += handler, async() => await service.Operation1(), 
        CancellationToken.None); 
     } 
     catch (InvalidOperationException) 
     { 
      //Exception is caught!!! 
     } 
    } 

    private static async Task Do2() 
    { 
     var service = new Service(new Hardware()); 
     try 
     { 
      await 
       OperationExecuter.ExecuteAsync(service, handler => service.Operation1Completed += handler, 
        handler => service.Operation1Completed += handler, async() => await service.Operation2(60), 
        CancellationToken.None); 
     } 
     catch (InvalidOperationException) 
     { 
      //Exception is caught!!! 
     } 
    } 
} 

internal class OperationExecuter 
{ 
    public static async Task ExecuteAsync(Service service, Action<EventHandler> subscriptionAction, 
     Action<EventHandler> unsubscriptionAction, Func<Task> sendCommandAction, CancellationToken cancellationToken) 
    { 
     var commandCompletionSource = new TaskCompletionSource<bool>(); 
     var hardwareFailureCompletionSource = new TaskCompletionSource<bool>(); 

     cancellationToken.Register(() => commandCompletionSource.SetCanceled()); 

     var eventHandler = new EventHandler((sender, args) => 
     { 
      commandCompletionSource.SetResult(true); 
     }); 

     service.HardwareFailure += (sender, args) => hardwareFailureCompletionSource.SetResult(false); 

     subscriptionAction(eventHandler); 

     try 
     { 
      await Task.Run(sendCommandAction, cancellationToken); 
      await Task.WhenAny(commandCompletionSource.Task, hardwareFailureCompletionSource.Task); 

      //same for disconnection, etc 
      if (hardwareFailureCompletionSource.Task.IsCompleted) 
      { 
       throw new HardwareFailureException(); 
      } 
     } 
     finally 
     { 
      unsubscriptionAction(eventHandler); 
     } 
    } 
} 

class HardwareFailureException : Exception 
{ 
} 

class Service 
{ 
    private readonly Hardware hardware; 

    public Service(Hardware hardware) 
    { 
     this.hardware = hardware; 
    } 

    public async Task Operation1() //something like sending command to hardware 
    { 
     await Task.Delay(1000); 
     throw new InvalidOperationException(); 
    } 

    public event EventHandler Operation1Completed; 

    public async Task Operation2(int someParameter) 
    { 
     await Task.Delay(1000); 
     throw new InvalidOperationException(); 
    } 

    public event EventHandler Operation2Completed; 

    public event EventHandler LostConnection; 

    public event EventHandler HardwareFailure; 
} 

class Hardware 
{ 
} 

回答

4

這個問題是由於事實上,你實際上創建了一個Task<Task>,而你只有await outter Task。這是你爲什麼不應該使用Task構造函數的原因之一。相反,使用Task.Run,這是意識到這一點,並且將解開你的outter任務:

private static async Task Do() 
{ 
    var task = Task.Run(async() => await Operation()); 
    try 
    { 
     await OperationExecuter.ExecuteAsync(task); 
    } 
    catch (InvalidOperationException) 
    { 
     //I expected the exception to be caught here 
    } 
} 

編輯

@Servy指出正確的,除非有一個特殊的很好的理由你正在用Task.Run包裝你的Task,你可以保存在一起,只需await在創建Task並節省你自己的解包麻煩:

public class OperationExecuter 
{ 
    public static async Task ExecuteAsync(Func<Task> func) 
    { 
     await func(); 
    } 
} 
+0

'OperationExecuter'控制何時開始任務是很重要的。我想我應該真的有'OperationExecuter'採取行動,並使用'Task.Run(行動)'然後,正確? –

+2

然後你應該改變你的實現。你不應該在寒冷的任務中傳遞信息。例如,使用'Task.Start'兩次會產生一個異常,並且也違反準則。相反,您可以將委託人排隊到您的'OperationExecutor'並使用'Task.Run'代替。 –

+0

不幸的是,改變實現以便'OperationExecuter'取而代之並使用'Task.Run'產生相同的結果。 –