C# - How to properly wait for data to be supplied by another class asynchronously?

227 Views Asked by At

I have an bunch of 'workers' which at some point need an unique key from my HTTP server to continue execution. This keys can sometimes arrive before the workers need them, in which case I just want to supply the worker with the key so it can continue execution. However if there are no keys left, I want the code execution in the task to be halted until a key is supplied. I also want the keys which where supplied first to be used first.

I have tried to implement a Singleton where the keys can be supplied to from the HTTP server and retrieved from by the workers, but execution seems to be deadlocked right now.

My code:

Keyhandler.cs

public sealed class KeyHandler
{
    private static readonly KeyHandler instance = new KeyHandler();

    private static Dictionary<Website, Queue<string>> retrievedKeys = new Dictionary<Website, Queue<string>>();
    private static Dictionary<Website, Queue<TaskCompletionSource<string>>> waitingWorkers = new Dictionary<Website, Queue<TaskCompletionSource<string>>>();

    // Explicit static constructor to tell C# compiler
    // not to mark type as beforefieldinit
    static KeyHandler()
    {
    }

    private KeyHandler()
    {
        foreach (Website website in Enum.GetValues(typeof(Website)))
        {
            retrievedKeys.Add(website, new Queue<string>());
            waitingWorkers.Add(website, new Queue<TaskCompletionSource<string>>());
        }
    }

    public static KeyHandler Instance
    {
        get
        {
            return instance;
        }
    }

    public static void AddKey(Website website, string response)
    {
        if (waitingWorkers[website].Count > 0)
        {
            waitingWorkers[website].Dequeue().SetResult(response);
        }
        else
        {
            retrievedKeys[website].Enqueue(response);
        }
    }

    public static string RetrieveKey(Website website)
    {
        if (retrievedKeys[website].Count > 0)
        {
            return retrievedKeys[website].Dequeue();
        } 
        else
        {
            TaskCompletionSource<string> tcs = new TaskCompletionSource<string>();
            Task<string> t = tcs.Task;
            waitingWorkers[website].Enqueue(tcs);

            return t.Result;
        }
    }
}

Worker.cs

    public async Task Run()
    {
        ...
        string key = KeyHandler.RetrieveKey(Website.MyWebsite);
        // do something with key here
        ...
    }

HTTPServer.cs

private void Process(HttpListenerContext context)
{
    ...
    KeyHandler.AddKey(Website.MyWebsite, key);
    ...
}
0

There are 0 best solutions below