2

I'm trying to "store" an async task for later completion - I've found the async cache example but this is effectively caching task results in a concurrent dictionary so that their results can be reloaded without re-doing the task again (the HTML implementation is here).

Basically what I'm trying to design is a dictionary of tasks, with correlation IDs (GUIDs) as the key. This is for co-ordinating incoming results from another place (XML identified by the GUID correlation ID) and my aim is for the task to suspend execution until the results come in (probably from a queue).

Is this going to work? This is my first foray into proper async coding and I can't find anything similar to my hopeful solution so I may well be entirely on the right track.

Can I effectively "store" a task for later completion, with the task result being set at completion time?

Edit: I've just found out about TaskCompletionSource (based on this question) is that viable?

Matt Hogan-Jones
  • 2,981
  • 1
  • 29
  • 35
  • You can create tasks and not await / wait() them, but it's bad practice to create lots of them, because there are limits to how many the computer can handle before things start jamming up. Generally you'd only store within the context of the method call, e.g. to get a user and a product from the database you can do both calls simultaneously. You'd be better off creating one or two async tasks that loop through a queue of operations, something like the CQRS pattern. – NibblyPig Mar 18 '19 at 16:42
  • @NibblyPig I think this is already taken care of in OP's design: `my aim is for the task to suspend execution until the results come in (probably from a queue).` – Kevin Gosse Mar 18 '19 at 16:49
  • 1
    It sounds like you need a dictionary of mutexes or some other kind of concurrency primitive, not a dictionary of tasks. Each running task would suspend itself simply by `await`ing something within the task implementation - that "something" should be some kind of signal that the needed data is available. – nlawalker Mar 18 '19 at 16:50
  • @NibblyPig I'm researching CQRS to see if it provides a solution, this idea using Tasks is just something else I wondered about and wanted to see if it was a viable solution – Matt Hogan-Jones Mar 18 '19 at 16:54
  • @nlawalker that's an interesting idea - I'll see if I can get something working. – Matt Hogan-Jones Mar 18 '19 at 16:55
  • Wouldn't a suspended task still take up thread pool space? – NibblyPig Mar 18 '19 at 17:04
  • @NibblyPig Not with a TaskCompletionSource. In that case, the Task is really just a facade that stores the status and the result. The queue can complete the tasks at its own pace and it'll scale perfectly – Kevin Gosse Mar 18 '19 at 17:23

2 Answers2

2

Are you thinking of lazy loading? You could use Lazy<Task> (which will initialise the task but not queue it to run).

var tasks = new Dictionary<Guid, Lazy<Task>>();

tasks.Add(Task1Guid, new Lazy<Task>(() => { whatever the 1st task is }));
tasks.Add(Task2Guid, new Lazy<Task>(() => { whatever the 2nd task is }));


void async RunTaskAsync(Guid guid)
{
   await tasks[guid].Value;
}
Neil
  • 11,059
  • 3
  • 31
  • 56
2

If I understand your use-case correctly, you can use TaskCompletionSource.

An example of implementation:

public class AsyncCache
{
    private Dictionary<Guid, Task<string>> _cache;

    public Task<string> GetAsync(Guid guid)
    {
        if (_cache.TryGetValue(guid, out var task))
        {
            // The value is either there or already queued
            return task;
        }

        var tcs = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);

        _queue.Enqueue(() => {
           var result = LoadResult();
           tcs.TrySetValue(result);
        });

        _cache.Add(guid, tcs.Task);

        return tcs.Task;            
    }
}

Here, _queue is whatever queuing mechanism you're going to use to process the data. Of course, you would also have to make that code thread-safe.

Kevin Gosse
  • 38,392
  • 3
  • 78
  • 94
  • I think this is very much the right direction. I am going to have to come back to this tomorrow but I do think TaskCompletionSource is where I'm going to end up. – Matt Hogan-Jones Mar 18 '19 at 17:42
  • I've accepted this answer because I think a TaskCompletionSource is right for me - I don't quite have a working implementation yet but I can suspend and then restart a task using them so it seems to be the right direction. – Matt Hogan-Jones Mar 19 '19 at 08:40