2016-05-09 15 views
1

Wenn ich von IIS eine Hintergrundaufgabe in einem neuen Thread aufruft, wird es nur ausgeführt, wenn die Task bestimmte asynchrone Aufrufe nicht enthält.Thread-Abbruch-Ausnahme in .NET 4.5

Wenn ich eine Hintergrundaufgabe in einem neuen Thread aufrufen, die diese asynchrone Aufrufe enthält, ist es eine ThreadAbortException zurückkehren, während die gleiche Aktion, innerhalb ApiController synchron ausgeführt wird, wird durchlaufen, und eine andere Aktion, genannt asynchron läuft auch durch.

Darüber hinaus, wenn ich eine Aktion synchron sowie die andere Aktion asynchron aufrufen, schlägt der asynchrone Aufruf ebenfalls fehl.

  • Was verursacht die ThreadAbortException?
  • Gibt es etwas, was ich tun kann, um die ThreadAbortException zu umgehen?

Code:

[HttpGet] 
public string TestThreadAbortException() 
{ 
    InitToolkit(); // Initialize Logger, DB etc. 
    DebugController.DoAfter(5.Seconds(), MyAction); // Runs through! 
    //TestThreadAbortException(logger); // Runs through! 
    //Combining the first and the second line makes the first one throw the Exception as well. 
    //DebugController.DoAfter(10.Seconds(), TestThreadAbortException); // throws Exception 
    return String.Join("\r\n",logger.Flush()); 
} 

private void TestThreadAbortException(Logger logger) 
{ 
    Task<string> task = new Task<string>(MyMethod); 
    task.Start(); 
    Task.Run(async() => await task); 
    try 
    { 
     var result = ConfigureAwait(task, false).Result; 
    } 
    catch (System.AggregateException ex) 
    { 
     if (ex.InnerExceptions.Count == 1) 
     { 
      throw ex.InnerExceptions[0]; 
     } 
     throw; 
    } 
} 

private async Task<string> ConfigureAwait(Task<string> task, bool continueOnCapturedContext) 
{ 
    return await task.ConfigureAwait(continueOnCapturedContext: continueOnCapturedContext); 
} 

private string MyMethod() 
{ 
    Thread.Sleep(20000); 
    return "Test"; 
} 

private void MyAction(Logger logger) 
{ 
    logger.Log(MyMethod()); 
} 

public static void DoAfter(TimeSpan waitFor, Action<Logger> action) 
{ 
    try { 
     ThreadStart work =() => 
     { 
      Thread.Sleep(waitFor); 
      DatabaseLogger logger = new DatabaseLogger(); 
      logger.Log("Executing " + action.Method.Name + ", " + DateTime.Now.ToLongTimeString()); 
      try 
      { 
       action.Invoke(logger); 
       logger.Log("Successfully executed " + action.Method.Name + ", " + DateTime.Now.ToLongTimeString()); 
      } 
      catch (Exception e) 
      { 
       logger.Log("Error in " + action.Method.Name + ": " + e.Message + ", " + DateTime.Now.ToLongTimeString()); 
      } 
      logger.CloseDatabase(); 
     }; 
     Thread thread = new Thread(work); 
     thread.Start(); 
    } 
    catch 
    { 
    } 
} 

Hintergrundinformation: In dem Produktionscode, der inneren async Anruf, in dem während des Debuggen ich erstellen Sie einfach eine neue Aufgabe, in einer Microsoft-Bibliothek erstellt wird, der nicht synchrone Methoden anbieten , so kann ich die Aufgabe nicht einfach "entfernen".

+0

Verwenden Sie 'new Thread' nicht mit' async'! –

+0

@DavidPine Warum nicht? Bitte erläutern. – Alexander

+0

Die Verwendung von 'async' und' await' wird dies für Sie erledigen ... es sollte nicht notwendig sein, einen neuen Thread manuell zu starten, besonders auf einem Webserver. –

Antwort

1

Was verursacht die ThreadAbortException?

Siehe ThreadAbortException: "Die Ausnahme, die ausgelöst wird, wenn ein Anruf an die Abort-Methode erfolgt". Vermeiden Sie die Verwendung des gesamten manuellen Thread Codes in der DoAfter Methode.

Gibt es etwas, was ich tun kann, um die ThreadAbortException zu umgehen?

Ja ... nutzen die async und await Suchbegriffe richtig folgende besten Programmiertechniken und Mustern.

Hier sind meine Vorschläge zu Änderungen:

[HttpGet] 
public async Task<string> TestThreadAbortException() 
{ 
    InitToolkit(); // Initialize Logger, DB etc. 

    var result = await DoAfter(5.Seconds(), MyAction); 

    return result; 
} 

Markieren Sie Ihre Controller-Methode als Task<T> zurückkehrte, wo T die Art zurückzukehren. In diesem Fall ein string.

Wenn Sie einfach einen Hintergrundprotokollierungsauftrag starten und dann zum Client zurückkehren müssen, sollten Sie QueueBackgroundWorkItem in Betracht ziehen. Anstatt Thread.SleepTask.Delay zu verwenden, markieren Sie Methoden wie Task oder Task<T>, die für asynchrone Operationen repräsentativ sind.

public async Task<T> DoAfter<T>(TimeSpan waitFor, Func<Logger, Task<T>> action) 
{ 
    await Task.Delay(waitFor); 
    DatabaseLogger logger = new DatabaseLogger(); 
    logger.Log("Executing " + action.Method.Name + ", " + DateTime.Now.ToLongTimeString()); 
    try 
    { 
     return await action(logger); 
     logger.Log("Successfully executed " + action.Method.Name + ", " + DateTime.Now.ToLongTimeString()); 
    } 
    catch (Exception e) 
    { 
     logger.Log("Error in " + action.Method.Name + ": " + e.Message + ", " + DateTime.Now.ToLongTimeString()); 
    } 
    finally 
    { 
     logger.CloseDatabase(); 
    } 
} 

private async Task MyAction(Logger logger) 
{ 
    var result = await MyMethod(); 
    logger.Log(result); 
} 

private async Task<string> MyMethod() 
{ 
    await Task.Delay(20000); 
    return "Test"; 
} 
+0

Ich denke 'QueueBackgroundWorkItem' ist genau das, was ich gesucht habe. Wird eine neue Frage für die Probleme eröffnen, die ich jetzt habe. – Alexander