C# SynchronizationContext.Send pauses UI thread

Viewed 182

I have this piece of code in a WPF control (used inside a WinForms app) called sometimes from the UI thread e "often" by a secondary thread using SynchronizationContext.Send():

private readonly DrawingGroup _backingStore;

public void Render()
{
    var drawingContext = _backingStore.Open();

    // draw rect etc....
    // drawingContext.DrawRect(...)

    drawingContext.Close();
}

In the default constructor of the control I take the SynchronizationContext of the UI thread: this.SynchContext = System.Threading.SynchronizationContext.Current;

When from the UI thread the method Render() is called "more often" then it can happen that an exception is thrown:

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)
   ...

The exception itself is clear, but I don't understand why it happens.

I added some logs:

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);
}

The result is this:

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

So Render() is always executed on the same thread (UI thread), but somehow it happens that while it is in the middle of an execution another call is executed ("in parallel").

I was expecting something (an event loop) behind the scenes that at the end makes the variuos calls serialized, since it is the same thread!

In fact this seems fixing the problem, note the absence of locks:

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

QUESTION 1

Can it happen that a call to SynchronizationContext.Send() makes somehow the UI thread "pause" what it is doing and execute the required code?

QUESTION 2

Since from the logs I see (through Thread.CurrentThread.ManagedThreadId) that the method is called from the same thread, is it correct using "private bool _rendering" without a lock?
(probably not because I don't know when the execution is paused, anyway depends by question 1)

0 Answers
Related