HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Cross Thread Access to Object in ViewModel

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
crossthreadviewmodelobjectaccess

Problem

This is a fairly straight forward question. I have a C# WPF MVVM application that call a C++ DLL to do some compilation/crunching. To update the UI C# application for progress updates I am using callback delegates.

public static class Callbacks
{
    public static ConsoleCallback ConsoleOutputCallback { get; set; }

    [UnmanagedFunctionPointer(CallingConvention.StdCall)]
    public delegate void ConsoleCallback(string message);
}


where the implementation is

Callbacks.ConsoleOutputCallback = (message) => 
    {
        console.Document.Text += message;
    };


where Document is of type TextDocument. I am then passing this object into the C++ to be able to invoke it and update the UI. This works great, however, for one control I am getting a standard


A first chance exception of type 'System.InvalidOperationException' ... TextDocument can be accessed only from the thread that owns it.

Now, I understand this error. To get around it I have done the following

TaskScheduler scheduler = TaskScheduler.FromCurrentSynchronizationContext();
Callbacks.ConsoleOutputCallback = (message) => 
    {
        Task.Factory.StartNew(() =>
        {
            console.Document.Text += message;
        }, CancellationToken.None,
           TaskCreationOptions.None,
           scheduler);
    };


which works. However, it would be nicer to look at if I could do a similar thing that I could do with a Control (which TextDocument Document is not) like

public static void PerformSafely(this Control target, Action action, T parameter)
{
    if (target.InvokeRequired)
        target.Invoke(action, parameter);
    else
        action(parameter);
}


but I cannot do this with TextDocument as it does not inherit from Control. Is there a nicer way to do what I am doing?

Solution

The TPL is unfortunately verbose at times. The only way I can think of to make it look "nicer" is to point ConsoleOutputCallback to a named method and delegate the creation of the Task to another method:

public static void MyMethod()
{
    Callbacks.ConsoleOutputCallback = ConsoleOutputCallback;
}

public static void ConsoleOutputCallback(string message)
{
    var scheduler = TaskScheduler.FromCurrentSynchronizationContext();

    Dispatch(
        msg => console.Document.Text += msg, 
        message, 
        scheduler
    );

    Dispatch(
        msg => someOtherNonControl.Text += msg,
        message,
        scheduler
    );
}

public static void Dispatch(Action action, T parameter, TaskScheduler scheduler)
{
    Task.Factory.StartNew(
        () => action(parameter), 
        CancellationToken.None, 
        TaskCreationOptions.None, 
        scheduler
    );
}

Code Snippets

public static void MyMethod()
{
    Callbacks.ConsoleOutputCallback = ConsoleOutputCallback;
}

public static void ConsoleOutputCallback(string message)
{
    var scheduler = TaskScheduler.FromCurrentSynchronizationContext();

    Dispatch(
        msg => console.Document.Text += msg, 
        message, 
        scheduler
    );

    Dispatch(
        msg => someOtherNonControl.Text += msg,
        message,
        scheduler
    );
}

public static void Dispatch<T>(Action<T> action, T parameter, TaskScheduler scheduler)
{
    Task.Factory.StartNew(
        () => action(parameter), 
        CancellationToken.None, 
        TaskCreationOptions.None, 
        scheduler
    );
}

Context

StackExchange Code Review Q#55317, answer score: 5

Revisions (0)

No revisions yet.