patterncsharpMinor
Cross Thread Access to Object in ViewModel
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.
where the implementation is
where
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
which works. However, it would be nicer to look at if I could do a similar thing that I could do with a
but I cannot do this with
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 standardA 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.