Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Task.Run using custom thread pool

Tags:

c#

async-await

I have to address a temporary situation that requires me to do a non-ideal thing: I have to call an async method from inside a sync one.

Let me just say here that I know all about the problems I'm getting myself into and I understand reasons why this is not advised.

That said, I'm dealing with a large codebase, which is completely sync from top to bottom and there is no way I can rewrite everything to use async await in a reasonable amount of time. But I do need to rewrite a number of small parts of this codebase to use the new async API that I'be been slowly developing over the last year or so, because it has a lot of new features that the old codebase would benefit from as well, but can't get them for legacy reasons. And since all that code isn't going away any time soon, I'm facing a problem.

TL;DR: A large sync codebase cannot be easily rewritten to support async but now requires calls into another large codebase, which is completely async.

I'm currently doing the simplest thing that works in the sync codebase: wrapping each async call into a Task.Run and waiting for the Result in a sync way.

The problem with this approach is, that it becomes slow whenever sync codebase does this in a tight loop. I understand the reasons and I sort of know what I can do - I'd have to make sure that all async calls are started on the same thread instead of borrowing a new one each time from the thread pool (which is what Task.Run does). This borrowing and returning incurs a lot of switching which can slow things down considerably if done a lot.

What are my options, short of writing my own scheduler that would prefer to reuse a single dedicated thread?

UPDATE: To better illustrate what I'm dealing with, I offer an example of one of the simplest transformations I need to do (there are more complex ones as well).

It's basically simple LINQ query that uses a custom LINQ provider under the hood. There's no EF or anything similar underneath.

[Old code]

var result = (from c in syncCtx.Query("Components")
    where c.Property("Id") == id
    select c).SingleOrDefault();

[New code]

var result = Task.Run(async () =>
{
    Dictionary<string, object> data;

    using (AuthorizationManager.Instance.AuthorizeAsInternal())
    {
        var uow = UnitOfWork.Current;
        var source = await uow.Query("Components")
            .Where("Id = @id", new { id })
            .PrepareAsync();
        var single = await source.SingleOrDefaultAsync();
        data = single.ToDictionary();
    }

    return data;
}).Result;

As mentioned, this is one of the less complicated examples and it already contains 2 async calls.

UPDATE 2: I tried removing the Task.Run and invoking .Result directly on the result of a wrapper async method, as suggested by @Evk and @Manu. Unfortunately, while testing this in my staging environment, I quickly ran into a deadlock. I'm still trying to understand what exactly transpired, but it's obvious that Task.Run cannot simply be removed in my case. There are additional complications to be resolved, first...

like image 457
aoven Avatar asked Sep 15 '25 15:09

aoven


1 Answers

I don't think you are on the right track. Wrapping every async call in a Task.Run seems horrible to me, it always starts an additional tasks which you don't need. But I understand that introducing async/await in a large codebase can be problematic.

I see a possible solution: Extract all async calls into separate, async methods. This way, your project will have a pretty nice transition from sync to async, since you can change methods one by one without affecting other parts of the code.

Something like this:

private Dictionary<string, object> GetSomeData(string id)
{
    var syncCtx = GetContext();
    var result = (from c in syncCtx.Query("Components")
                  where c.Property("Id") == id
                  select c).SingleOrDefault();
    DoSomethingSyncWithResult(result);
    return result;
}

would become something like this:

private Dictionary<string, object> GetSomeData(string id)
{
    var result = FetchComponentAsync(id).Result;
    DoSomethingSyncWithResult(result);
    return result;
}

private async Task<Dictionary<string, object>> FetchComponentAsync(int id)
{
    using (AuthorizationManager.Instance.AuthorizeAsInternal())
    {
        var uow = UnitOfWork.Current;
        var source = await uow.Query("Components")
            .Where("Id = @id", new { id })
            .PrepareAsync();
        var single = await source.SingleOrDefaultAsync();
        return single.ToDictionary();
    }
}

Since you are in a Asp.Net environment, mixing sync with async is a very bad idea. I'm surprised that your Task.Run solution works for you. The more you incorporate the new async codebase into the old sync codebase, the more you will run into problems and there is no easy fix for that, except rewriting everything in an async way.

I strongly suggest you to not mix your async parts into the sync codebase. Instead, work from "bottom to top", change everything from sync to async where you need to await an async call. It may seem like a lot of work, but the benefits are much higher than if you search for some "hacks" now and don't fix the underlining problems.

like image 93
Manuel Allenspach Avatar answered Sep 18 '25 08:09

Manuel Allenspach