Excel 开发中与线程相关的若干问题参考这个链接:
SynchronizationContext的实现参考这个链接(共3篇文章,进入链接后可以找到下一章的链接):
https://www.codeproject.com/Articles/31971/Understanding-SynchronizationContext-Part-I
Excel是一种STA线程的应用程序,使用多线程直接怼会出问题,必须借助线程的同步上下文Send Or Post 消息。
在ExcelDNA中给出了一个解决方案:
1 dynamic app; 2 dynamic worksheet; 3 4 public void Test(IRibbonControl ribbonControl) 5 { 6 app = ExcelDnaUtil.Application; 7 worksheet = app.ActiveSheet; 8 worksheet.Cells[2, 2].Value = Thread.CurrentThread.ManagedThreadId; 9 Action<object> action = Run; 10 Task task = new Task(action,ExcelSynchronizationContext.Current); 11 task.Start(); 12 13 } 14 private void Run(object context) 15 { 16 worksheet.Cells[1, 2].Value = Thread.CurrentThread.ManagedThreadId; 17 18 for (int i = 0; i < 50; i++) 19 { 20 Thread.Sleep(100); 21 ExcelAsyncUtil.QueueAsMacro(UpdateUI, i); 22 } 23 } 24 25 private void UpdateUI(object state) 26 { 27 int i = (int)state + 1; 28 worksheet.Cells[i, 1].Value = i; 29 worksheet.Cells[i, 3].Value = Thread.CurrentThread.ManagedThreadId; 30 }
Task task = new Task(action,ExcelSynchronizationContext.Current);
ExcelSynchronizationContext.Current为主线程的SynchronizationContext。
ExcelAsyncUtil.QueueAsMacro(UpdateUI, i);
其实是执行了SynchronizationContext的Post方法。
上面是ExcelDNA里集成的SynchronizationContext实现,有兴趣可以→
尝试实现自定义一个SynchronizationContext
一个SendOrPostCallbackItem实例为一组SendOrPostCallback委托与参数
1 internal enum ExecutionType 2 { 3 Post, 4 Send 5 } 6 internal class SendOrPostCallbackItem 7 { 8 object state; 9 ExecutionType executionType; 10 SendOrPostCallback callbackMethod; 11 12 internal SendOrPostCallbackItem(SendOrPostCallback callback, object state, ExecutionType executionType) 13 { 14 this.callbackMethod = callback; 15 this.state = state; 16 this.executionType = executionType; 17 } 18 19 internal void Execute() 20 { 21 if (executionType == ExecutionType.Post) 22 Post(); 23 else 24 Send(); 25 } 26 internal void Send() 27 { 28 throw new NotImplementedException(); 29 } 30 31 internal void Post() 32 { 33 callbackMethod(state); 34 } 35 }
DiyExcelSynchronizationContext
在ExcelDNA下实现一个SynchronizationContext
public class DiyExcelSynchronizationContext : SynchronizationContext { private const string RunMacroName = "ExcelSyncContext.Run"; private static readonly ConcurrentQueue<SendOrPostCallbackItem> queue = new ConcurrentQueue<SendOrPostCallbackItem>(); private static readonly TimeSpan BackoffTime = TimeSpan.FromSeconds(1); [ExcelCommand(Name = RunMacroName)] public static void Run() { while (true) { SendOrPostCallbackItem workItem; if (!queue.TryDequeue(out workItem)) { return; } workItem.Execute(); } } dynamic application = ExcelDnaUtil.Application; public override void Post(SendOrPostCallback d, object state) { SendOrPostCallbackItem item = new SendOrPostCallbackItem(d, state, ExecutionType.Post); queue.Enqueue(item); Task.Factory.StartNew(() => { while (true) { try { application.Run(RunMacroName); break; } catch (COMException e1) { if (IsRetry(e1)) { Thread.Sleep(BackoffTime); continue; } return; } catch (Exception e2) { return; } } }); } public const uint RPC_E_SERVERCALL_RETRYLATER = 0x8001010A; public const uint RPC_E_CANTCALLOUT_INASYNCCALL = 0x800AC472; public static bool IsRetry(COMException e) { var errorCode = (uint)e.ErrorCode; switch (errorCode) { case RPC_E_SERVERCALL_RETRYLATER: case RPC_E_CANTCALLOUT_INASYNCCALL: return true; default: return false; } } }