C# 同步上下文。发送暂停UI线程

C# 同步上下文。发送暂停UI线程,c#,wpf,windows,multithreading,winforms,C#,Wpf,Windows,Multithreading,Winforms,我在一个WPF控件(在WinForms应用程序中使用)中有这段代码,有时由辅助线程使用SynchronizationContext.Send()从UI线程e“经常”调用: 在控件的默认构造函数中,我采用UI线程的SynchronizationContext: this.SynchContext=System.Threading.SynchronizationContext.Current 当从UI线程调用Render()方法时,可能会引发异常: System.InvalidOperationEx

我在一个WPF控件(在WinForms应用程序中使用)中有这段代码,有时由辅助线程使用
SynchronizationContext.Send()从UI线程e“经常”调用:

在控件的默认构造函数中,我采用UI线程的SynchronizationContext:
this.SynchContext=System.Threading.SynchronizationContext.Current

当从UI线程调用Render()方法时,可能会引发异常:

System.InvalidOperationException: This object has an outstanding DrawingContext. The DrawingContext must be Closed or Disposed before making Open or Append calls.
   in System.Windows.Forms.Control.MarshaledInvoke(Control caller, Delegate method, Object[] args, Boolean synchronous)
   in System.Windows.Forms.Control.Invoke(Delegate method, Object[] args)
   in System.Windows.Forms.WindowsFormsSynchronizationContext.Send(SendOrPostCallback d, Object state)
   ...
例外情况本身很清楚,但我不明白为什么会发生

我添加了一些日志:

private Random _rand = new Random();

public void Render()
{
    int  r = _rand.Next(); // just to "group" the logs for the same call

    _logger.debug("START {0} {1}", Thread.CurrentThread.ManagedThreadId, r);
    
    try {
        var drawingContext = _backingStore.Open();
    
        // draw rect etc....
    
        drawingContext.Close();
        
    } catch(Exception e) {
        _logger.debug("ERROR X {0} {1}", Thread.CurrentThread.ManagedThreadId, r);
        throw e;
    }
    
    _logger.debug("END {0} {1}", Thread.CurrentThread.ManagedThreadId, r);
}
结果是:

START 1 123
END 1 123
START 1 567
END 1 567
START 1 223344
START 1 999888777
ERROR X 1 999888777
END 1 223344
START 1 876
END 1 876

这样,RelDead()总是在同一个线程(UI线程)上执行,但是不知何故,当它在执行的中间时,执行另一个调用(“并行”)。 我希望幕后会有某种东西(一个事件循环),最终会使variuos调用序列化,因为它是同一个线程

事实上,这似乎解决了问题,请注意没有锁:

private bool _rendering;
private Random _rand = new Random();

public void Render()
{
    int r = _rand.Next();

    _logger.debug("START {0} {1}", Thread.CurrentThread.ManagedThreadId, r);
    
    if(_rendering) {
        _logger.debug("Already rendering! {0} {1}", Thread.CurrentThread.ManagedThreadId, r);
        return;
    }
    _rendering = true;
    
    try {
        var drawingContext = _backingStore.Open();
    
        // draw rect etc....
    
        drawingContext.Close();
        
    } catch(Exception e) {
        _logger.debug("ERROR X {0} {1}", Thread.CurrentThread.ManagedThreadId, r);
        throw e;
    }
    
    _rendering = false;
    
    _logger.debug("END {0} {1}", Thread.CurrentThread.ManagedThreadId, r);
}
问题1 调用
SynchronizationContext.Send()
会不会让UI线程“暂停”正在执行的操作并执行所需的代码

问题2 从日志中我看到(通过
Thread.CurrentThread.ManagedThreadId
)该方法是从同一个线程调用的,因此使用不带锁的“private bool\u rendering”是否正确

(可能不是因为我不知道执行何时暂停,这取决于问题1)

windowsformsssynchronizationcontext.Send()
是同步的,由调用
Invoke()
引起。所以它显然是阻塞的。您的意思可能是可以同时调用
Render()
方法。在这种情况下,您必须添加一个锁。可能有一个过程可以访问该方法,而不是在混合模式下使用它。您可以将代码更改为使用
Progress
委托,该委托使用
SynchronizationContext.Post()
。如果并发调用,仍然需要锁。是的,Render()方法可以并发调用:直接由UI线程调用,或由另一个线程使用
WindowsFormsSynchronizationContext.Send()
。Send()正在阻塞,这对我来说没关系。无论如何,我希望Render()调用的执行是序列化的,因为它们应该在同一个线程(UI线程)中执行,但是序列化没有发生。请重新定义您的过程。设置
\u rendering=true不是线程安全的。当/如果它工作时,将阻止绘图。除非这是期望的结果。使
\u呈现
线程安全是可以的,但始终从同一UI线程访问它,即使并发执行发生。
\u rendering
变量是对
SynchronizationContext.Send()
行为的临时修复。您看到的不是由
SynchronizationContext.Send()
引起的,而是由不同线程并发调用同一委托而不关心状态引起的。--您还没有发布这些工作线程是什么,以及实际调用此方法的方式/时间。无论如何,要尽可能保证线程安全(并且,如上所述,尽量避免对该方法的混合模式调用)。
private bool _rendering;
private Random _rand = new Random();

public void Render()
{
    int r = _rand.Next();

    _logger.debug("START {0} {1}", Thread.CurrentThread.ManagedThreadId, r);
    
    if(_rendering) {
        _logger.debug("Already rendering! {0} {1}", Thread.CurrentThread.ManagedThreadId, r);
        return;
    }
    _rendering = true;
    
    try {
        var drawingContext = _backingStore.Open();
    
        // draw rect etc....
    
        drawingContext.Close();
        
    } catch(Exception e) {
        _logger.debug("ERROR X {0} {1}", Thread.CurrentThread.ManagedThreadId, r);
        throw e;
    }
    
    _rendering = false;
    
    _logger.debug("END {0} {1}", Thread.CurrentThread.ManagedThreadId, r);
}
START 1 123
END 1 123
START 1 567
END 1 567
START 1 223344
START 1 999888777
Already rendering! 1 999888777
END 1 223344
START 1 876
END 1 876