Code snippet for a method dealing with IDs from multiple threads
Here are my thoughts on the code that you provided:


You should mark your lockObj and runningIds as readonly
You are using the presence of the queue in the dictionary as a signal that another thread is currently working on specific ID which is not quite obvious
You mix non-blocking (await) approach along with blocking (ManualResetEvent.WaitOne). It would be better to stick to one of them (preferably non-blocking).
If you change the approach from "notify the guy after me that he can start working" to "I'm done", then you would be able to capture only the last "guy" against specific ID. "I'm done" is easily modelled by Task.


As a result, my first refactoring of your code looks like this:

private static readonly object _lockObj = new object();
private static readonly Dictionary&lt;int, Task&gt; _runningIds = new Dictionary&lt;int, Task&gt;();

public async Task TheMethod(int id, string val)
{
    //Will be used to signal others that the work has finished
    var completionSource = new TaskCompletionSource&lt;bool&gt;();

    Task currentTask;

    lock (_lockObj)
    {
        _runningIds.TryGetValue(id, out currentTask);
        _runningIds[id] = completionSource.Task;
    }

    if (currentTask != null)
        await currentTask; 

    // DO LONG RUNNING TASK
    await Task.Delay(3000);
    Console.WriteLine(id + " " + val + " at time " + DateTime.Now.ToString("m:ss"));

    completionSource.SetResult(true);

    //cleanup
    lock (_lockObj)
    {
        if (_runningIds.TryGetValue(id, out currentTask) &amp;&amp; currentTask == completionSource.Task)
            _runningIds.Remove(id);
    }
}


Second take was mostly as an exercise to get rid of lock at all by using the ConcurrentDictionary.

private static readonly ConcurrentDictionary&lt;int, Task&gt; _runningIds = new ConcurrentDictionary&lt;int, Task&gt;();

private static Task RegisterAsLastScheduledTask(int id, Task task)
{
    Task previousTask = null;

    //Either adding a new task to the dictionary, or capturing and replacing previous task
    while (!_runningIds.TryAdd(id, task) &amp;&amp;
        !(_runningIds.TryGetValue(id, out previousTask) &amp;&amp; _runningIds.TryUpdate(id, task, previousTask)))
    {
        previousTask = null;
    }

    return previousTask;
}

public async Task TheMethod(int id, string val)
{
    //Will be used to signal others that the work has finished
    var completionSource = new TaskCompletionSource&lt;bool&gt;();
    Task previousTask = RegisterAsLastScheduledTask(id, completionSource.Task);

    if (previousTask != null)
        await previousTask;

    // DO LONG RUNNING TASK
    await Task.Delay(3000);
    Console.WriteLine(id + " " + val + " at time " + DateTime.Now.ToString("m:ss"));

    completionSource.SetResult(true);

    //"hack" to atomically remove the element only if both key and value match, see http://blogs.msdn.com/b/pfxteam/archive/2011/04/02/10149222.aspx
    ((ICollection&lt;KeyValuePair&lt;int, Task&gt;&gt;)_runningIds).Remove(new KeyValuePair&lt;int, Task&gt;(id, completionSource.Task));
}