Async method call from ThreadPool thread deadlocks
I know that there are similar questions, but I tried every best practice explained there, but the code still deadlocks.
The async method is called from a System.Timers.Timer
Elapsed
event with SynchronizingObject set to null, so the events will fire on the ThreadPool
. So actually with Task.Run().Result
run on the ThreadPool
, I could also get deadlocks because Task.Run
fires threads on the ThreadPool...
I need to do a REST call using HttpClient
from a Windows Service which fires some System.Timers.Timer
event every few seconds on the ThreadPool
. Because HttpClient
only has Async methods, I need to call async methods from the ThreadPool
.
I tried every best practice that is being told here: https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html
At the moment it's just not an option to make the whole app async compatible because it would require a lot of refactoring. I need to make it work from synchronous code.
So in fact, I await the HttpClient
method like this:
await HttpClient.PostAsync().ConfigureAwait(false);
And every await has also ConfigureAwait(false);
until the place where I have to get the result of the Task.
I tried .Result
and .GetAwaiter().GetResult()
but these cause deadlocks and code hangs infinitely. Then I have to kill the service process from Task Manager...
Any help on how I can get the result from the Task without deadlocking the service?
I didn't try Task.Run<>(async () => await HttpClient.PostAsync());
yet, would this solve my issue? But it looks like a hack...
And please don't tell me to use some external Nuget Package, there should be a way without depending on an external package.
Make your Elapsed event async. In your comment, you mentioned that you don't want to do this because the Elapsed event can be triggered again before the first one is finished. To avoid that, simply set AutoReset
to false
when you create your timer so that it doesn't automatically restart the timer. Then restart it at the end of your Elapsed event:
private async void OnTimedEvent(object source, ElapsedEventArgs e) {
try {
// Do stuff
} finally {
// Restart the timer
timer.Enabled = true;
}
}
The try
/finally
block ensures that the timer is restarted even if there is an exception. You can add a catch
block in there too if needed.
This way, you don't need to use lock
in your Elapsed event.