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.