• 1000 более
Post
в остальной части кода после
await
перехода к
SynchronizationContext
.
Я создал очень очень базовый c и НЕ оптимальный SynchronizationContext
, чтобы продемонстрировать, как базовый c реализация должна выглядеть так. Моя реализация создаст новый Thread
и запустит некоторые Task
s в определенном c контексте внутри того же вновь созданного Thread
.
Можно найти лучшую реализацию (но гораздо сложнее) здесь в репозитории Стивена Клири на GitHub.
Моя реализация в основном выглядит следующим образом (из моего репозитория GitHub , код в репозитории может выглядеть иначе в будущем):
/// <summary>
/// This <see cref="SynchronizationContext"/> will call all posted callbacks in a single new thread.
/// </summary>
public class SingleNewThreadSynchronizationContext : SynchronizationContext
{
readonly Thread _workerThread;
readonly BlockingCollection<KeyValuePair<SendOrPostCallback, object>> _actionStatePairs = new BlockingCollection<KeyValuePair<SendOrPostCallback, object>>();
/// <summary>
/// Returns the Id of the worker <see cref="Thread"/> created by this <see cref="SynchronizationContext"/>.
/// </summary>
public int ManagedThreadId => _workerThread.ManagedThreadId;
public SingleNewThreadSynchronizationContext()
{
// Creates a new thread to run the posted calls.
_workerThread = new Thread(() =>
{
try
{
while (true)
{
var actionStatePair = _actionStatePairs.Take();
SetSynchronizationContext(this);
actionStatePair.Key?.Invoke(actionStatePair.Value);
}
}
catch (ThreadAbortException)
{
Console.WriteLine($"The thread {_workerThread.ManagedThreadId} of {nameof(SingleNewThreadSynchronizationContext)} was aborted.");
}
});
_workerThread.IsBackground = true;
_workerThread.Start();
}
public override void Post(SendOrPostCallback d, object state)
{
// Queues the posted callbacks to be called in this SynchronizationContext.
_actionStatePairs.Add(new KeyValuePair<SendOrPostCallback, object>(d, state));
}
public override void Send(SendOrPostCallback d, object state)
{
throw new NotSupportedException();
}
public override void OperationCompleted()
{
_actionStatePairs.Add(new KeyValuePair<SendOrPostCallback, object>(new SendOrPostCallback(_ => _workerThread.Abort()), null));
_actionStatePairs.CompleteAdding();
}
}
и вот демонстрация для его использования:
static void SingleNewThreadSynchronizationContextDemo()
{
var synchronizationContext = new SingleNewThreadSynchronizationContext();
// Creates some tasks to test that the whole calls in the tasks (before and after awaiting) will be called in the same thread.
for (int i = 0; i < 20; i++)
Task.Run(async () =>
{
SynchronizationContext.SetSynchronizationContext(synchronizationContext);
// Before yielding, the task will be started in some thread-pool thread.
var threadIdBeforeYield = Thread.CurrentThread.ManagedThreadId;
// We yield to post the rest of the task after await to the SynchronizationContext.
// Other possiblity here is maybe to start the whole Task using a different TaskScheduler.
await Task.Yield();
var threadIdBeforeAwait1 = Thread.CurrentThread.ManagedThreadId;
await Task.Delay(100);
var threadIdBeforeAwait2 = Thread.CurrentThread.ManagedThreadId;
await Task.Delay(100);
Console.WriteLine($"SynchronizationContext: thread Id '{synchronizationContext.ManagedThreadId}' | type '{SynchronizationContext.Current?.GetType()}.'");
Console.WriteLine($"Thread Ids: Before yield '{threadIdBeforeYield}' | Before await1 '{threadIdBeforeAwait1}' | Before await2 '{threadIdBeforeAwait2}' | After last await '{Thread.CurrentThread.ManagedThreadId}'.{Environment.NewLine}");
});
}
static void Main(string[] args)
{
Console.WriteLine($"Entry thread {Thread.CurrentThread.ManagedThreadId}");
SingleNewThreadSynchronizationContextDemo();
Console.WriteLine($"Exit thread {Thread.CurrentThread.ManagedThreadId}");
Console.ReadLine();
}
Вывод:
Entry thread 1
Exit thread 1
SynchronizationContext: thread Id '5' | type 'SynchronizationContexts.SingleNewThreadSynchronizationContext.'
Thread Ids: Before yield '11' | Before await1 '5' | Before await2 '5' | After last await '5'.
SynchronizationContext: thread Id '5' | type 'SynchronizationContexts.SingleNewThreadSynchronizationContext.'
Thread Ids: Before yield '4' | Before await1 '5' | Before await2 '5' | After last await '5'.
SynchronizationContext: thread Id '5' | type 'SynchronizationContexts.SingleNewThreadSynchronizationContext.'
Thread Ids: Before yield '12' | Before await1 '5' | Before await2 '5' | After last await '5'.
SynchronizationContext: thread Id '5' | type 'SynchronizationContexts.SingleNewThreadSynchronizationContext.'
Thread Ids: Before yield '6' | Before await1 '5' | Before await2 '5' | After last await '5'.
SynchronizationContext: thread Id '5' | type 'SynchronizationContexts.SingleNewThreadSynchronizationContext.'
Thread Ids: Before yield '10' | Before await1 '5' | Before await2 '5' | After last await '5'.
SynchronizationContext: thread Id '5' | type 'SynchronizationContexts.SingleNewThreadSynchronizationContext.'
Thread Ids: Before yield '7' | Before await1 '5' | Before await2 '5' | After last await '5'.