调用SynchronizationContext.Post时SendOrPostCallback未在同线程执行的问题
我准备了一个C#示例,多次运行会在日志中看到异常,在Visual Studio 2022或VS Code中编译运行可查看详细错误信息:
System.InvalidOperationException
HResult=0x80131509
Message=Operations that change non-concurrent collections must have exclusive access. A concurrent update was performed on this collection and corrupted its state. The collection's state is no longer correct.
Source=System.Private.CoreLib
StackTrace:
at System.ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported()
at System.Collections.Generic.Dictionary2.TryInsert(TKey key, TValue value, InsertionBehavior behavior) at System.Collections.Generic.Dictionary2.set_Item(TKey key, TValue value)
at SyncContextTest.PlayloadAccumulationHandler.AddCallback(Object state) in PlayloadAccumulationHandler.cs:line 38
at System.Threading.QueueUserWorkItemCallbackDefaultContext`1.Execute()
at System.Threading.ThreadPoolWorkQueue.Dispatch()
at System.Threading.PortableThreadPool.WorkerThread.WorkerThreadStart()
测试用例
Program.cs(创建100个线程调用handler.Add())
class Program { private const int NUM_THREADS = 100; private static PayloadAccumulationHandler handler = new(); static void Main(string[] args) { ThreadStart ts = new(RunMe); Thread[] threads = Enumerable.Range(0, NUM_THREADS) .Select(_ => new Thread(ts)) .ToArray(); foreach (Thread t in threads) { t.Start(); } foreach (Thread t in threads) { t.Join(); } } private static void RunMe() { int threadId = Environment.CurrentManagedThreadId; handler.Add("microsoft", "de", (uint)(threadId % 3)); } }
PayloadAccumulationHandler.cs(使用SynchronizationContext.Post())
public class PayloadAccumulationHandler { private static readonly SynchronizationContext SyncContextTest = SynchronizationContext.Current ?? new SynchronizationContext(); private static Dictionary<string, uint> dict = []; public void Add(string brand, string country, uint useCaseId) { Console.WriteLine($"Add({brand}, {country}, {useCaseId})"); string key = $"{brand}/{country}/{useCaseId}"; SyncContextTest.Post(AddCallback, key); } public void AddCallback(object? state) { int threadId = Environment.CurrentManagedThreadId; if (state is string key) { Console.WriteLine($"AddCallback: {threadId} {key}"); if (dict.TryGetValue(key, out uint value)) { dict[key] = value + 1; } else { dict[key] = 1; // 抛出System.InvalidOperationException! } } } }
疑问
我特意用Dictionary而非ConcurrentDictionary,目的是验证AddCallback是否在同一线程执行,但实际并非如此。我是否操作有误,或是存在错误假设?
我原本期望SynchronizationContext.Post()类似Android Handler,能确保回调在同一线程执行。另外,控制台应用中SynchronizationContext.Current为null,新建的实例该如何配置才能让回调在单线程执行?
内容的提问来源于stack exchange,提问作者Alexander Farber

