Reputation: 20224
From IIS, when I am calling some background task in a new thread, it only runs through if the task does not contain certain asynchronous calls.
If I call a background task in a new thread, that does contain these asynchronous calls, it does return a ThreadAbortException
, while the same Action, being executed synchronously inside ApiController
, does run through, and a different action, called asynchronously, also runs through.
Furthermore, when I call one action synchronously as well as the other action asynchronously, the asynchronous call fails as well.
ThreadAbortException
?ThreadAbortException
?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
{
}
}
Background information: In the production code, the inner async call, where during debugging I just create a new task, is created inside a Microsoft library that does not offer synchronous methods, so I won't be able to just "remove the Task".
Upvotes: 1
Views: 1319
Reputation: 24525
What causes the ThreadAbortException?
See ThreadAbortException
: "The exception that is thrown when a call is made to the Abort method". Avoid using all the manual Thread
related code in the DoAfter
method.
Is there anything I can do to get around the ThreadAbortException?
Yes... utilize the async
and await
keywords correctly following best programming practices and patterns.
Here are my suggested modifications:
[HttpGet]
public async Task<string> TestThreadAbortException()
{
InitToolkit(); // Initialize Logger, DB etc.
var result = await DoAfter(5.Seconds(), MyAction);
return result;
}
Mark your controller method as Task<T>
returning, where T
is the type to return. In this case a string
.
If you need to simply start a background logging job and then return to the client, you should consider QueueBackgroundWorkItem
. Instead of using Thread.Sleep
use Task.Delay
, mark methods as Task
or Task<T>
that are representative of asynchronous operations.
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";
}
Upvotes: 1