c# MVVM InvalidOperationException on BackgroundWorker when using Dispatcher - c#

i'm currently facing an issue in C# WPF. I wrote an application, that generates long running reports in a background task. I am using prism with MVVM and trying to run the expensive background task with a Async ICommand implementation and a BackgroundWorker. But when i try to retrieve the resulting report
Report = asyncTask.Result;
i get an InvalidOperationException stating "The calling thread cannot access this object because a different thread owns it.".
Yes, i have already tried to invoke a dispatcher (its the first thing you'll find on google, stackoverflow etc when you search for the exception message). I have tried several variants like for instance:
Dispatcher.CurrentDispatcher.Invoke(() => Report = asyncTaks.Result);
or
Report.Dispatcher.Invoke(() => Report = asyncTask.Result);
but each time i get this exception.
I am suspecting that the way i am calling the report UI is not adequate.
The structure looks in brief as follows:
MainWindowViewModel
-> SubWindowCommand
SubWindowViewModel
-> GenerateReportCommand
ReportViewModel
-> GenerateReportAsyncCommand
<- Exception on callback
I am out of ideas, does anybody have a clue what i might be doing wrong?
Below are a few code fragments
Report Generator View Model:
public class ReportFlowDocumentViewModel : BindableBase
{
private IUnityContainer _container;
private bool _isReportGenerationInProgress;
private FlowDocument _report;
public FlowDocument Report
{
get { return _report; }
set
{
if (object.Equals(_report, value) == false)
{
SetProperty(ref _report, value);
}
}
}
public bool IsReportGenerationInProgress
{
get { return _isReportGenerationInProgress; }
set
{
if (_isReportGenerationInProgress != value)
{
SetProperty(ref _isReportGenerationInProgress, value);
}
}
}
public ReportFlowDocumentView View { get; set; }
public DelegateCommand PrintCommand { get; set; }
public AsyncCommand GenerateReportCommand { get; set; }
public ReportFlowDocumentViewModel(ReportFlowDocumentView view, IUnityContainer c)
{
_container = c;
view.DataContext = this;
View = view;
view.ViewModel = this;
InitializeGenerateReportAsyncCommand();
IsReportGenerationInProgress = false;
}
private void InitializeGenerateReportAsyncCommand()
{
GenerateReportCommand = new CreateReportAsyncCommand(_container);
GenerateReportCommand.RunWorkerStarting += (sender, args) =>
{
IsReportGenerationInProgress = true;
var reportGeneratorService = new ReportGeneratorService();
_container.RegisterInstance<ReportGeneratorService>(reportGeneratorService);
};
GenerateReportCommand.RunWorkerCompleted += (sender, args) =>
{
IsReportGenerationInProgress = false;
var report = GenerateReportCommand.Result as FlowDocument;
var dispatcher = Application.Current.MainWindow.Dispatcher;
try
{
dispatcher.VerifyAccess();
if (Report == null)
{
Report = new FlowDocument();
}
Dispatcher.CurrentDispatcher.Invoke(() =>
{
Report = report;
});
}
catch (InvalidOperationException inex)
{
// here goes my exception
}
};
}
public void TriggerReportGeneration()
{
GenerateReportCommand.Execute(null);
}
}
This is how i start the ReportView Window
var reportViewModel = _container.Resolve<ReportFlowDocumentViewModel>();
View.ReportViewerWindowAction.WindowContent = reportViewModel.View;
reportViewModel.TriggerReportGeneration();
var popupNotification = new Notification()
{
Title = "Report Viewer",
};
ShowReportViewerRequest.Raise(popupNotification);
with
ShowReportViewerRequest = new InteractionRequest<INotification>();
AsyncCommand definition
public abstract class AsyncCommand : ICommand
{
public event EventHandler CanExecuteChanged;
public event EventHandler RunWorkerStarting;
public event RunWorkerCompletedEventHandler RunWorkerCompleted;
public abstract object Result { get; protected set; }
private bool _isExecuting;
public bool IsExecuting
{
get { return _isExecuting; }
private set
{
_isExecuting = value;
if (CanExecuteChanged != null)
CanExecuteChanged(this, EventArgs.Empty);
}
}
protected abstract void OnExecute(object parameter);
public void Execute(object parameter)
{
try
{
onRunWorkerStarting();
var worker = new BackgroundWorker();
worker.DoWork += ((sender, e) => OnExecute(e.Argument));
worker.RunWorkerCompleted += ((sender, e) => onRunWorkerCompleted(e));
worker.RunWorkerAsync(parameter);
}
catch (Exception ex)
{
onRunWorkerCompleted(new RunWorkerCompletedEventArgs(null, ex, true));
}
}
private void onRunWorkerStarting()
{
IsExecuting = true;
if (RunWorkerStarting != null)
RunWorkerStarting(this, EventArgs.Empty);
}
private void onRunWorkerCompleted(RunWorkerCompletedEventArgs e)
{
IsExecuting = false;
if (RunWorkerCompleted != null)
RunWorkerCompleted(this, e);
}
public virtual bool CanExecute(object parameter)
{
return !IsExecuting;
}
}
CreateReportAsyncCommand:
public class CreateReportAsyncCommand : AsyncCommand
{
private IUnityContainer _container;
public CreateReportAsyncCommand(IUnityContainer container)
{
_container = container;
}
public override object Result { get; protected set; }
protected override void OnExecute(object parameter)
{
var reportGeneratorService = _container.Resolve<ReportGeneratorService>();
Result = reportGeneratorService?.GenerateReport();
}
}

I think i understand my problem now. I cannot use FlowDocument in a BackgroundThread and update it afterwards, right?
So how can i create a FlowDocument within a background thread, or at least generate the document asynchronously?
The FlowDocument i am creating contains a lot of tables and when i run the report generation synchronously, the UI freezes for about 30seconds, which is unacceptable for regular use.
EDIT:
Found the Solution here:
Creating FlowDocument on BackgroundWorker thread
In brief: I create a flow document within my ReportGeneratorService and then i serialize the FlowDocument to string. In my background worker callback i receive the serialized string and deserialize it - both with XamlWriter and XmlReader as shown here

Your Problem is that you create FlowDocument in another thread. Put your data to the non GUI container and use them after bg comes back in UI thread.

Related

C# How to access label element (defined in designer) from a different class using background worker

I don't know how to acces a label element (myLabel) from a different class outside my Control Class while using background worker.
I really don't know the correct syntax.
Thanks a lot.
This is my code:
public partial class BasicControl : UserControl
{
// ...
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
this.Invoke(new MethodInvoker(delegate { myLabel.Text = "THIS WORKS!"; }));
var moc = new MyOtherClass();
string result = moc.myMethod();
}
}
internal class MyOtherClass
{
public void myMethod()
{
myLabel.Text = "THIS DOES NOT WORK!"; // NOT WORKING
}
}
The DoWorkEventArgs class that you pass in your code is intended to allow interactions between the caller and the worker thread using its Argument property. In the following example it will be set to an instance of CustomDoWorkContext that implements INotifyPropertyChanged. Pass it when you start the worker bw_DoWork(sender, args: new DoWorkEventArgs(argument: context)) and have the worker pass args on to the method string result = moc.myMethod(args). This way, the outer, calling class receives PropertyChanged notifications while the thread is running whenever a bound property changes.
internal class MyOtherClass
{
internal string myMethod(DoWorkEventArgs args)
{
if (args.Argument is CustomDoWorkContext context)
{
context.Message = $"THIS WORKS! # {DateTime.Now} ";
}
return "Your result";
}
}
Here is the custom class that creates context for the worker thread.
class CustomDoWorkContext : INotifyPropertyChanged
{
public CustomDoWorkContext(CancellationToken cancellationToken)
{
Token = cancellationToken;
}
// This class can have as many bindable properties as you need
string _message = string.Empty;
public string Message
{
get => _message;
set => SetProperty(ref _message, value);
}
int _count = 0;
public int Count
{
get => _count;
set => SetProperty(ref _count, value);
}
public CancellationToken Token { get; }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetProperty<T>( ref T backingStore, T value, [CallerMemberName] string propertyName = "")
{
if (EqualityComparer<T>.Default.Equals(backingStore, value)) return false;
backingStore = value;
OnPropertyChanged(propertyName);
return true;
}
}
The key point is to subscribe to the PropertyChanged event before starting the background worker.
// Get notified when any property changes in CustomDoWorkContext.
var context = new CustomDoWorkContext(cancellationToken: _cts.Token);
context.PropertyChanged += (sender, e) =>
{
switch (e.PropertyName)
{
case nameof(CustomDoWorkContext.Message):
Invoke((MethodInvoker)delegate
// When myMethod sets Message, change the label text.
{ myLabel.Text = $"{context.Message}"; });
break;
case nameof(CustomDoWorkContext.Count):
// When the count increments, change the checkbox text.
Invoke((MethodInvoker)delegate
{ checkBoxDoWork.Text = $"Count = {context.Count}"; });
break;
}
};
Then start the worker by passing the args (something like this).
// Set the `Argument` property when you start the worker.
// (Like this or something similar.)
Task.Run(() => bw_DoWork(sender, args: new DoWorkEventArgs(argument: context)));
Have the worker thread pass the args along to the myMethod:
private async void bw_DoWork(object sender, DoWorkEventArgs args)
{
var moc = new MyOtherClass();
if (args.Argument is CustomDoWorkContext context)
{
args.Cancel = context.Token.IsCancellationRequested;
while (!args.Cancel) // Loop for testing
{
context.Count++;
string result = moc.myMethod(args); // Pass the args
try { await Task.Delay(1000, context.Token); }
catch (TaskCanceledException) { return; }
}
}
}
TEST
BasicControl:UserControl with checkbox and label is placed on the MainForm.
Toggling the checkbox starts and stops the worker after instantiating CustomDoWorkContext.

Backgroundworker exits after first expression

I have a list view bound to an ObservableCollection. With that I want to mock a chat application with a WPF gui.
To simulate some activity I wanted to use a Background worker who spams a little bit. But the worker always exits his loop after executing the first statment, so my question is: why does he do that and how to fix it?
here is the code so far:
public partial class MainWindow : Window, INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
private string pCurrentUsername;
public string currentUsername
{
get { return pCurrentUsername; }
set
{
pCurrentUsername = value;
if (null != this.PropertyChanged)
{
PropertyChanged(this, new PropertyChangedEventArgs("currentUsername"));
}
}
}
ObservableCollection<ChatPost> items = new ObservableCollection<ChatPost>();
BackgroundWorker bgWorker = new BackgroundWorker();
public MainWindow()
{
InitializeComponent();
currentUsername = "Me";
items.Add(new ChatPost("this", "that"));
bgWorker.DoWork += new DoWorkEventHandler(mockBussiness);
bgWorker.RunWorkerAsync();
lvChat.ItemsSource = items;
}
private void mockBusiness(object o, DoWorkEventArgs args)
{
while (!bgWorker.CancellationPending)
{
items.Add(new ChatPost("guy1", "Ey man!"));
items.Add(new ChatPost("guy2", "What man?"));
}
}
private void btSend_Click(object sender, RoutedEventArgs e)
{
items.Add(new ChatPost(currentUsername, tbMessage.Text));
}
}
public class ChatPost
{
public ChatPost()
{ }
public ChatPost(string username, string message)
{
this.username = username;
this.message = message;
}
public string username { get; set; }
public string message { get; set; }
}
So the only thing that gets executed (meaning printed) is one time "Ey man!"
Yes, you're modifying the UI (indirectly, through the ObservableCollection<>) on a non-UI thread. You're not allowed to do that. I suspect you should find an exception being thrown giving that detail, although it may not be easy to find.
You need to marshal back to the UI thread for any threading operations, in general. If you're using WPF on .NET 4.5, apparently you can using BindingOperations.EnableCollectionSynchronization for this, but I admit I have no direct experience of this.

How to update a ListBox if an element was changed c#

Hi,
I'm struggling a bit using the ListBox.DataSource and the INotifyPropertyChanged Interface. I checked several posts about this issue already but I cannot figure out, how to update the view of a ListBox if an element of the bound BindingList is changed.
I basically want to change the color of an IndexItem after the content has been parsed.
Here the relevant calls in my form:
btn_indexAddItem.Click += new EventHandler(btn_indexAddItem_Click);
lst_index.DataSource = Indexer.Items;
lst_index.DisplayMember = "Url";
lst_index.DrawItem += new DrawItemEventHandler(lst_index_DrawItem);
private void btn_indexAddItem_Click(object sender, EventArgs e)
{
Indexer.AddSingleURL(txt_indexAddItem.Text);
}
private void lst_index_DrawItem(object sender, DrawItemEventArgs e)
{
IndexItem item = lst_index.Items[e.Index] as IndexItem;
if (item != null)
{
e.DrawBackground();
SolidBrush brush = new SolidBrush((item.hasContent) ? SystemColors.WindowText : SystemColors.ControlDark);
e.Graphics.DrawString(item.Url, lst_index.Font, brush, 0, e.Index * lst_index.ItemHeight);
e.DrawFocusRectangle();
}
}
Indexer.cs:
class Indexer
{
public BindingList<IndexItem> Items { get; }
private object SyncItems = new object();
public Indexer()
{
Items = new BindingList<IndexItem>();
}
public void AddSingleURL(string url)
{
IndexItem item = new IndexItem(url);
if (!Items.Contains(item))
{
lock (SyncItems)
{
Items.Add(item);
}
new Thread(new ThreadStart(() =>
{
// time consuming parsing
Thread.Sleep(5000);
string content = item.Url;
lock (SyncItems)
{
Items[Items.IndexOf(item)].Content = content;
}
}
)).Start();
}
}
}
IndexItem.cs
class IndexItem : IEquatable<IndexItem>, INotifyPropertyChanged
{
public int Key { get; }
public string Url { get; }
public bool hasContent { get { return (_content != null); } }
private string _content;
public string Content {
get
{
return (hasContent) ? _content : "empty";
}
set
{
_content = value;
ContentChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void ContentChanged()
{
if (this.PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Content"));
}
}
public IndexItem(string url)
{
this.Key = url.GetHashCode();
this.Url = url;
}
public override bool Equals(object obj)
{
return Equals(obj as IndexItem);
}
public override int GetHashCode()
{
return Key;
}
public bool Equals(IndexItem other)
{
if (other == null) return false;
return (this.Key.Equals(other.Key)) ||
((hasContent || other.hasContent) && (this._content.Equals(other._content)));
}
public override string ToString()
{
return Url;
}
}
Any ideas what went wrong and how to fix it? I'll appreciate any hint...
It seems to me that the control should redraw when it raises the ListChanged event for that item. This will force it to do so:
lst_index.DrawItem += new DrawItemEventHandler(lst_index_DrawItem);
Indexer.Items.ListChanged += Items_ListChanged;
private void Items_ListChanged(object sender, ListChangedEventArgs e)
{
lst_index.Invalidate(); // Force the control to redraw when any elements change
}
So why doesn't it do that already? Well, it seems that the listbox only calls DrawItem if both DisplayMember changed, and if the INotifyPropertyChanged event was raised from the UI thread. So this also works:
lock (SyncItems)
{
// Hacky way to do an Invoke
Application.OpenForms[0].Invoke((Action)(() =>
{
Items[Items.IndexOf(item)].Url += " "; // Force listbox to call DrawItem by changing the DisplayMember
Items[Items.IndexOf(item)].Content = content;
}));
}
Note that calling PropertyChanged on the Url is not sufficient. The value must actually change. This tells me that the listbox is caching those values. :-(
(Tested with VS2015 REL)

Why does opening and closing a window speed up my application? - C# WPF MVVM Observer Pattern

I have a C# WPF application built with Visual Studio 2015. I'm using MVVM and the Observer Pattern.
My Provider is a user control called 'ucClientFilter1ViewModel' that contains two text box controls where the user can search for a client(s):
namespace NSUCClientControls
{
public class ucClientFilter1ViewModel : ViewModelBase, IObservable<ClientFilterParameter>
{
private string filterLocation;
private string whereSearch1;
private string whereSearch2;
private List<IObserver<ClientFilterParameter>> observers;
public ucClientFilter1ViewModel()
{
observers = new List<IObserver<ClientFilterParameter>>();
}
public string FilterLocation
{
get { return filterLocation; }
set { filterLocation = value; }
}
public string WhereSearch1
{
get { return whereSearch1; }
set
{
whereSearch1 = value;
TestUpdateGrid(filterLocation);
}
}
public string WhereSearch2
{
get { return whereSearch2; }
set
{
whereSearch2 = value;
TestUpdateGrid(filterLocation);
}
}
private void TestUpdateGrid(string _filterLocation)
{
var filterInfo = new ClientFilterParameter(this);
foreach (var observer in observers)
{
observer.OnNext(filterInfo);
}
}
public IDisposable Subscribe(IObserver<ClientFilterParameter> observer)
{
// Check whether observer is already registered. If not, add it
if (!observers.Contains(observer))
{
observers.Add(observer);
// Provide observer with existing data
var filterInfo = new ClientFilterParameter(this);
observer.OnNext(filterInfo);
}
return new Unsubscriber<ClientFilterParameter>(observers, observer);
}
internal class Unsubscriber<ClientFilterParameter> : IDisposable
{
private IObserver<ClientFilterParameter> observer;
private List<IObserver<ClientFilterParameter>> observers;
public Unsubscriber(List<IObserver<ClientFilterParameter>> _observers, IObserver<ClientFilterParameter> _observer)
{
observers = _observers;
observer = _observer;
}
public void Dispose()
{
if (observers.Contains(observer))
{
observers.Remove(observer);
}
}
}
}
}
My Observer is a user control called 'ucClientGrid1ViewModel' that contains a datagrid where the search results are displayed.
namespace NSUCClientControls
{
public class ucClientGrid1ViewModel : ViewModelBase, IObserver<ClientFilterParameter>
{
private IDisposable cancellation;
private ObservableCollection<Client> clientsMultiple;
public ucClientGrid1ViewModel()
{
}
public ObservableCollection<Client> ClientsMultiple
{
get
{
var myClientDataAccess = new ClientDataAccess();
clientsMultiple = myClientDataAccess.GetClientListFromSQL_Test2();
return clientsMultiple;
}
set
{
}
}
public virtual void Subscribe(ucClientFilter1ViewModel provider)
{
cancellation = provider.Subscribe(this);
}
public void OnNext(ClientFilterParameter myFilter)
{
OnPropertyChanged("ClientsMultiple");
var myDummyWindow = new dummyWindow();
myDummyWindow.Show();
myDummyWindow.Close();
}
public void OnError(Exception error)
{
throw new NotImplementedException();
}
public void OnCompleted()
{
throw new NotImplementedException();
}
}
}
This all works and I get the search results that I am expecting. But what I don't understand is why the inclusion of the following lines actually speed things up!
var myDummyWindow = new dummyWindow();
myDummyWindow.Show();
myDummyWindow.Close();
I'm new to MVVM and the observer pattern, so as I was writing the code I had included message boxes at various points to help me to follow the flow of it. It was all working as expected. Then I removed the message boxes and it still worked but the application was pausing at the end before you could continue to keep searching.
Putting a message box back in at the end prevented this pause. Replacing the message box with a "DummyWindow" that just opens and closes has the same affect and prevents the pause at the end. This is what I currently have but I'd rather not leave this in there.
Presumably opening the window causes something else to happen which stops some redundant process, and this then prevents the pause? What else could I do to prevent the pause at the end, without using this DummyWindow?
I've tried searching on here and with Bing with no luck.
Thanks in advance!
Edit:
ViewModelBase...
namespace NSCommon
{
public abstract class ViewModelBase : INotifyPropertyChanged, IDisposable
{
protected ViewModelBase()
{
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
public void Dispose()
{
OnDispose();
}
protected virtual void OnDispose()
{
}
}
}
ClientFilterParameter...
namespace NSCommon
{
public class ClientFilterParameter
{
public ClientFilterParameter(ucClientFilter1ViewModel myFilter)
{
FilterLocation = myFilter.FilterLocation;
WhereSearch1 = myFilter.WhereSearch1;
WhereSearch2 = myFilter.WhereSearch2;
}
private string filterLocation;
private string whereSearch1;
private string whereSearch2;
public string FilterLocation
{
get { return filterLocation; }
set { filterLocation = value; }
}
public string WhereSearch1
{
get { return whereSearch1; }
set { whereSearch1 = value; }
}
public string WhereSearch2
{
get { return whereSearch2; }
set { whereSearch2 = value; }
}
}
}

How to raise an action through an interactivity trigger at an unittest?

i got some little problems. I want to write some unittest fora c#/wpf/interactivty project with visual studio 2010. and dont forget im a beginner, so sorry for that ;)
the unittest should simulate a (virtual) Key Down Event on a textbox and the result should raise an action.(Action result: Console output - just to check as first step)
i still fixed 2 problems -> the dispatcher problem & the presentationSource bug.
The unittest still simulates the keyevent and the keyevent reached the textbox but the question is, why the action not raised through the keydown event on the textbox?
It's a threading problem? what's my missunderstand?
here is the code
The Unittest
at the end of the unittest u could check the textbox - the keyboard works
[TestMethod]
public void simpleTest()
{
var mockWindow = new MockWindow();
//simple test to check if the virtualKeyboard works
string CheckText = "Checktext";
mockWindow.SendToUIThread(mockWindow.textbox, CheckText);
mockWindow.SendToUIThread(mockWindow.textbox, "k");
//needed to start the dispatcher
DispatcherUtil.DoEvents();
}
the Dispatcher fix
public static class DispatcherUtil
{
[SecurityPermissionAttribute(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)]
public static void DoEvents()
{
DispatcherFrame frame = new DispatcherFrame();
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background, new DispatcherOperationCallback(ExitFrame), frame);
Dispatcher.PushFrame(frame);
}
private static object ExitFrame(object frame)
{
((DispatcherFrame)frame).Continue = false;
return null;
}
}
My Testaction
class TestAction : TriggerAction<UIElement>
{
protected override void Invoke(object parameter)
{
Console.WriteLine("testAction invoke");
}
}
The MockWindow
public class MockWindow : Window
{
public TextBox textbox { get; private set; }
public MockWindow()
{
//add a grid&textbox
Grid grid = new Grid();
textbox = new TextBox();
this.Content = grid;
grid.Children.Add(textbox);
//create the testaction/triggerEvent & add them
TestAction testAction = new TestAction();
System.Windows.Interactivity.EventTrigger TestTrigger = new System.Windows.Interactivity.EventTrigger();
TestTrigger.EventName = "KeyDown";
TestTrigger.Actions.Add(testAction);
TestTrigger.Attach(this.textbox);
}
//enter a keyboard press on an UIElement
public void SendToUIThread(UIElement element, string text)
{
element.Dispatcher.BeginInvoke(new Action(() =>
{
SendKeys.Send(element, text);
}), DispatcherPriority.Input);
}
}
the MockKeyboard added from codeplex sendkeys + a presentationCore fix for unittest(added at class SendKeys)
public class FixPresentationSource : PresentationSource
{
protected override CompositionTarget GetCompositionTargetCore()
{
return null;
}
public override Visual RootVisual { get; set; }
public override bool IsDisposed { get { return false; } }
}

Categories