Если вы можете использовать .NET 4.6 или более позднюю версию, .NET Standard или .NET Core, они решили эту проблему с помощью AsyncLocal.
https://docs.microsoft.com/en-gb/dotnet/api/system.threading.asynclocal-1?view=netframework-4.7.1
Если нет, вам необходимо настроить хранилище данных до его использования и получить к нему доступ через замыкание, а не поток или задачу. ConcurrentDictionary поможет скрыть любые ошибки, которые вы совершаете, делая это.
Когда код ожидает, текущая задача освобождает поток - то есть потоки не связаны с задачами, по крайней мере, в модели программирования.
Демо-версия:
// I feel like demo code about threading needs to guarantee
// it actually has some in the first place :)
// The second number is IOCompletionPorts which would be relevant
// if we were using IO (strangely enough).
var threads = Environment.ProcessorCount * 4;
ThreadPool.SetMaxThreads(threads, threads);
ThreadPool.SetMinThreads(threads, threads);
var rand = new Random(DateTime.Now.Millisecond);
var tasks = Enumerable.Range(0, 50)
.Select(_ =>
{
// State store tied to task by being created in the same closure.
var taskState = new ConcurrentDictionary<string, object>();
// There is absolutely no need for this to be a thread-safe
// data structure in this instance but given the copy-pasta,
// I thought I'd save people some trouble.
return Task.Run(async () =>
{
taskState["ThreadId"] = Thread.CurrentThread.ManagedThreadId;
await Task.Delay(rand.Next() % 100);
return Thread.CurrentThread.ManagedThreadId == (int)taskState["ThreadId"];
});
})
.ToArray();
Task.WaitAll(tasks);
Console.WriteLine("Tasks that stayed on the same thread: " + tasks.Count(t => t.Result));
Console.WriteLine("Tasks that didn't stay on the same thread: " + tasks.Count(t => !t.Result));