Related
My code is as below
public CountryStandards()
{
InitializeComponent();
try
{
FillPageControls();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Country Standards", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
/// <summary>
/// Fills the page controls.
/// </summary>
private void FillPageControls()
{
popUpProgressBar.IsOpen = true;
lblProgress.Content = "Loading. Please wait...";
progress.IsIndeterminate = true;
worker = new BackgroundWorker();
worker.DoWork += new System.ComponentModel.DoWorkEventHandler(worker_DoWork);
worker.ProgressChanged += new System.ComponentModel.ProgressChangedEventHandler(worker_ProgressChanged);
worker.WorkerReportsProgress = true;
worker.WorkerSupportsCancellation = true;
worker.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
worker.RunWorkerAsync();
}
private void worker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
GetGridData(null, 0); // filling grid
}
private void worker_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
{
progress.Value = e.ProgressPercentage;
}
private void worker_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
{
worker = null;
popUpProgressBar.IsOpen = false;
//filling Region dropdown
Standards.UDMCountryStandards objUDMCountryStandards = new Standards.UDMCountryStandards();
objUDMCountryStandards.Operation = "SELECT_REGION";
DataSet dsRegionStandards = objStandardsBusinessLayer.GetCountryStandards(objUDMCountryStandards);
if (!StandardsDefault.IsNullOrEmptyDataTable(dsRegionStandards, 0))
StandardsDefault.FillComboBox(cmbRegion, dsRegionStandards.Tables[0], "Region", "RegionId");
//filling Currency dropdown
objUDMCountryStandards = new Standards.UDMCountryStandards();
objUDMCountryStandards.Operation = "SELECT_CURRENCY";
DataSet dsCurrencyStandards = objStandardsBusinessLayer.GetCountryStandards(objUDMCountryStandards);
if (!StandardsDefault.IsNullOrEmptyDataTable(dsCurrencyStandards, 0))
StandardsDefault.FillComboBox(cmbCurrency, dsCurrencyStandards.Tables[0], "CurrencyName", "CurrencyId");
if (Users.UserRole != "Admin")
btnSave.IsEnabled = false;
}
/// <summary>
/// Gets the grid data.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="pageIndex">Index of the page.( used in case of paging) </pamam>
private void GetGridData(object sender, int pageIndex)
{
Standards.UDMCountryStandards objUDMCountryStandards = new Standards.UDMCountryStandards();
objUDMCountryStandards.Operation = "SELECT";
objUDMCountryStandards.Country = txtSearchCountry.Text.Trim() != string.Empty ? txtSearchCountry.Text : null;
DataSet dsCountryStandards = objStandardsBusinessLayer.GetCountryStandards(objUDMCountryStandards);
if (!StandardsDefault.IsNullOrEmptyDataTable(dsCountryStandards, 0) && (chkbxMarketsSearch.IsChecked == true || chkbxBudgetsSearch.IsChecked == true || chkbxProgramsSearch.IsChecked == true))
{
DataTable objDataTable = StandardsDefault.FilterDatatableForModules(dsCountryStandards.Tables[0], "Country", chkbxMarketsSearch, chkbxBudgetsSearch, chkbxProgramsSearch);
dgCountryList.ItemsSource = objDataTable.DefaultView;
}
else
{
MessageBox.Show("No Records Found", "Country Standards", MessageBoxButton.OK, MessageBoxImage.Information);
btnClear_Click(null, null);
}
}
The step objUDMCountryStandards.Country = txtSearchCountry.Text.Trim() != string.Empty ? txtSearchCountry.Text : null; in get grid data throws exception
The calling thread cannot access this object because a different
thread owns it.
What's wrong here?
This is a common problem with people getting started. Whenever you update your UI elements from a thread other than the main thread, you need to use:
this.Dispatcher.Invoke(() =>
{
...// your code here.
});
You can also use control.Dispatcher.CheckAccess() to check whether the current thread owns the control. If it does own it, your code looks as normal. Otherwise, use above pattern.
To add my 2 cents, the exception can occur even if you call your code through System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke(). The point is that you have to call Invoke() of the Dispatcher of the control that you're trying to access, which in some cases may not be the same as System.Windows.Threading.Dispatcher.CurrentDispatcher. So instead you should use YourControl.Dispatcher.Invoke() to be safe. I was banging my head for a couple of hours before I realized this.
Update
For future readers, it looks like this has changed in the newer versions of .NET (4.0 and above). Now you no longer have to worry about the correct dispatcher when updating UI-backing properties in your VM. WPF engine will marshal cross-thread calls on the correct UI thread. See more details here. Thanks to #aaronburro for the info and link. You may also want to read our conversation below in comments.
Update 2
Since this is a popular post now, I thought I'd share my experience that I had in the following years. The behavior seems to be that any property bindings will update correctly in cross-thread calls (no marshalling required; WPF will handle it for you). OTOH command bindings will need to be delegated to the UI dispatcher. I have tested it with both MVVM Light and the relatively new Community Toolkit and it seems to be the case with both the old Framework and the new .NET 5 and 6. AsyncRelayCommand fails to update the UI when invoked from non-UI thread (This happens when CanExecuteChanged is fired from a worker thread which updates, for example, button's Enabled property). The solution of course is to store UI dispatcher somewhere in the global space in your VM upon startup and then use it when updating the UI.
If you encounter this problem and UI Controls were created on a separate worker thread when working with BitmapSource or ImageSource in WPF, call Freeze() method first before passing the BitmapSource or ImageSource as a parameter to any method. Using Application.Current.Dispatcher.Invoke() does not work in such instances
this happened with me because I tried to access UI component in another thread insted of UI thread
like this
private void button_Click(object sender, RoutedEventArgs e)
{
new Thread(SyncProcces).Start();
}
private void SyncProcces()
{
string val1 = null, val2 = null;
//here is the problem
val1 = textBox1.Text;//access UI in another thread
val2 = textBox2.Text;//access UI in another thread
localStore = new LocalStore(val1);
remoteStore = new RemoteStore(val2);
}
to solve this problem, wrap any ui call inside what Candide mentioned above in his answer
private void SyncProcces()
{
string val1 = null, val2 = null;
this.Dispatcher.Invoke((Action)(() =>
{//this refer to form in WPF application
val1 = textBox.Text;
val2 = textBox_Copy.Text;
}));
localStore = new LocalStore(val1);
remoteStore = new RemoteStore(val2 );
}
You need to do it on the UI thread. Use:
Dispatcher.BeginInvoke(new Action(() => {GetGridData(null, 0)}));
For some reason Candide's answer didn't build. It was helpful, though, as it led me to find this, which worked perfectly:
System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke((Action)(() =>
{
//your code here...
}));
This works for me.
new Thread(() =>
{
Thread.CurrentThread.IsBackground = false;
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, (SendOrPostCallback)delegate {
//Your Code here.
}, null);
}).Start();
As mentioned here, Dispatcher.Invoke could freeze the UI. Should use Dispatcher.BeginInvoke instead.
Here is a handy extension class to simplify the checking and calling dispatcher invocation.
Sample usage: (call from WPF window)
this Dispatcher.InvokeIfRequired(new Action(() =>
{
logTextbox.AppendText(message);
logTextbox.ScrollToEnd();
}));
Extension class:
using System;
using System.Windows.Threading;
namespace WpfUtility
{
public static class DispatcherExtension
{
public static void InvokeIfRequired(this Dispatcher dispatcher, Action action)
{
if (dispatcher == null)
{
return;
}
if (!dispatcher.CheckAccess())
{
dispatcher.BeginInvoke(action, DispatcherPriority.ContextIdle);
return;
}
action();
}
}
}
I also found that System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke() is not always dispatcher of target control, just as dotNet wrote in his answer. I didn't had access to control's own dispatcher, so I used Application.Current.Dispatcher and it solved the problem.
The problem is that you are calling GetGridData from a background thread. This method accesses several WPF controls which are bound to the main thread. Any attempt to access them from a background thread will lead to this error.
In order to get back to the correct thread you should use SynchronizationContext.Current.Post. However in this particular case it seems like the majority of the work you are doing is UI based. Hence you would be creating a background thread just to go immediately back to the UI thread and do some work. You need to refactor your code a bit so that it can do the expensive work on the background thread and then post the new data to the UI thread afterwards
There are definitely different ways to do this depending on your needs.
One way I use a UI-updating thread (that's not the main UI thread) is to have the thread start a loop where the entire logical processing loop is invoked onto the UI thread.
Example:
public SomeFunction()
{
bool working = true;
Thread t = new Thread(() =>
{
// Don't put the working bool in here, otherwise it will
// belong to the new thread and not the main UI thread.
while (working)
{
Application.Current.Dispatcher.Invoke(() =>
{
// Put your entire logic code in here.
// All of this code will process on the main UI thread because
// of the Invoke.
// By doing it this way, you don't have to worry about Invoking individual
// elements as they are needed.
});
}
});
}
With this, code executes entirely on main UI thread. This can be a pro for amateur programmers that have difficulty wrapping their heads around cross-threaded operations. However, it can easily become a con with more complex UIs (especially if performing animations). Really, this is only to fake a system of updating the UI and then returning to handle any events that have fired in lieu of efficient cross-threading operations.
Also, another solution is ensuring your controls are created in UI thread, not by a background worker thread for example.
I kept getting the error when I added cascading comboboxes to my WPF application, and resolved the error by using this API:
using System.Windows.Data;
private readonly object _lock = new object();
private CustomObservableCollection<string> _myUiBoundProperty;
public CustomObservableCollection<string> MyUiBoundProperty
{
get { return _myUiBoundProperty; }
set
{
if (value == _myUiBoundProperty) return;
_myUiBoundProperty = value;
NotifyPropertyChanged(nameof(MyUiBoundProperty));
}
}
public MyViewModelCtor(INavigationService navigationService)
{
// Other code...
BindingOperations.EnableCollectionSynchronization(AvailableDefectSubCategories, _lock );
}
For details, please see https://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k(System.Windows.Data.BindingOperations.EnableCollectionSynchronization);k(TargetFrameworkMoniker-.NETFramework,Version%3Dv4.7);k(DevLang-csharp)&rd=true
Sometimes it can be the object you created that throws the exception, not the target where I was obviously looking at.
In my code here:
xaml file:
<Grid Margin="0,0,0,0" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" >
<TextBlock x:Name="tbScreenLog" VerticalAlignment="Stretch" Background="Black" FontSize="12" Foreground="#FF919191" HorizontalAlignment="Stretch"/>
</Grid>
xaml.cs file:
System.Windows.Documents.Run rnLine = new System.Windows.Documents.Run(Message.Item2 + "\r\n");
rnLine.Foreground = LineAlternate ? Brushes.Green : Brushes.Orange;
Dispatcher.Invoke(()=> {
tbScreenLog.Inlines.Add(rnLine);
});
LineAlternate = !LineAlternate;
I got the exception about accessing an object from a different thread but I was invoking it on the UI thread??
After a while it daunted on me that it was not about the TextBlock object but about the Run object I created before invoking.
Changing the code to this solved my problem:
Dispatcher.Invoke(()=> {
Run rnLine = new Run(Message.Item2 + "\r\n");
rnLine.Foreground = LineAlternate ? Brushes.Green : Brushes.Orange;
tbScreenLog.Inlines.Add(rnLine);
});
LineAlternate = !LineAlternate;
I encountered this error strangely on the second item selected from a WPF Control.
The reason was that I loaded the data into a RX SourceCache, and the loaded elements had ObservableCollections as Navigation Properties wrapped into a CollectionView. The ObservableCollections are connected to the UIThread and the data was loaded by the WorkerThread. As the CollectionView was only populated on displaying the first element, the issue with the different thread only occured on the second item being selected.
Solution would be to move the sublists to the ViewModel as ReadOnlyObservableCollections and filter the full list of the sub element table by the currently selected main element.
My code is as below
public CountryStandards()
{
InitializeComponent();
try
{
FillPageControls();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Country Standards", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
/// <summary>
/// Fills the page controls.
/// </summary>
private void FillPageControls()
{
popUpProgressBar.IsOpen = true;
lblProgress.Content = "Loading. Please wait...";
progress.IsIndeterminate = true;
worker = new BackgroundWorker();
worker.DoWork += new System.ComponentModel.DoWorkEventHandler(worker_DoWork);
worker.ProgressChanged += new System.ComponentModel.ProgressChangedEventHandler(worker_ProgressChanged);
worker.WorkerReportsProgress = true;
worker.WorkerSupportsCancellation = true;
worker.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
worker.RunWorkerAsync();
}
private void worker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
GetGridData(null, 0); // filling grid
}
private void worker_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
{
progress.Value = e.ProgressPercentage;
}
private void worker_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
{
worker = null;
popUpProgressBar.IsOpen = false;
//filling Region dropdown
Standards.UDMCountryStandards objUDMCountryStandards = new Standards.UDMCountryStandards();
objUDMCountryStandards.Operation = "SELECT_REGION";
DataSet dsRegionStandards = objStandardsBusinessLayer.GetCountryStandards(objUDMCountryStandards);
if (!StandardsDefault.IsNullOrEmptyDataTable(dsRegionStandards, 0))
StandardsDefault.FillComboBox(cmbRegion, dsRegionStandards.Tables[0], "Region", "RegionId");
//filling Currency dropdown
objUDMCountryStandards = new Standards.UDMCountryStandards();
objUDMCountryStandards.Operation = "SELECT_CURRENCY";
DataSet dsCurrencyStandards = objStandardsBusinessLayer.GetCountryStandards(objUDMCountryStandards);
if (!StandardsDefault.IsNullOrEmptyDataTable(dsCurrencyStandards, 0))
StandardsDefault.FillComboBox(cmbCurrency, dsCurrencyStandards.Tables[0], "CurrencyName", "CurrencyId");
if (Users.UserRole != "Admin")
btnSave.IsEnabled = false;
}
/// <summary>
/// Gets the grid data.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="pageIndex">Index of the page.( used in case of paging) </pamam>
private void GetGridData(object sender, int pageIndex)
{
Standards.UDMCountryStandards objUDMCountryStandards = new Standards.UDMCountryStandards();
objUDMCountryStandards.Operation = "SELECT";
objUDMCountryStandards.Country = txtSearchCountry.Text.Trim() != string.Empty ? txtSearchCountry.Text : null;
DataSet dsCountryStandards = objStandardsBusinessLayer.GetCountryStandards(objUDMCountryStandards);
if (!StandardsDefault.IsNullOrEmptyDataTable(dsCountryStandards, 0) && (chkbxMarketsSearch.IsChecked == true || chkbxBudgetsSearch.IsChecked == true || chkbxProgramsSearch.IsChecked == true))
{
DataTable objDataTable = StandardsDefault.FilterDatatableForModules(dsCountryStandards.Tables[0], "Country", chkbxMarketsSearch, chkbxBudgetsSearch, chkbxProgramsSearch);
dgCountryList.ItemsSource = objDataTable.DefaultView;
}
else
{
MessageBox.Show("No Records Found", "Country Standards", MessageBoxButton.OK, MessageBoxImage.Information);
btnClear_Click(null, null);
}
}
The step objUDMCountryStandards.Country = txtSearchCountry.Text.Trim() != string.Empty ? txtSearchCountry.Text : null; in get grid data throws exception
The calling thread cannot access this object because a different
thread owns it.
What's wrong here?
This is a common problem with people getting started. Whenever you update your UI elements from a thread other than the main thread, you need to use:
this.Dispatcher.Invoke(() =>
{
...// your code here.
});
You can also use control.Dispatcher.CheckAccess() to check whether the current thread owns the control. If it does own it, your code looks as normal. Otherwise, use above pattern.
To add my 2 cents, the exception can occur even if you call your code through System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke(). The point is that you have to call Invoke() of the Dispatcher of the control that you're trying to access, which in some cases may not be the same as System.Windows.Threading.Dispatcher.CurrentDispatcher. So instead you should use YourControl.Dispatcher.Invoke() to be safe. I was banging my head for a couple of hours before I realized this.
Update
For future readers, it looks like this has changed in the newer versions of .NET (4.0 and above). Now you no longer have to worry about the correct dispatcher when updating UI-backing properties in your VM. WPF engine will marshal cross-thread calls on the correct UI thread. See more details here. Thanks to #aaronburro for the info and link. You may also want to read our conversation below in comments.
Update 2
Since this is a popular post now, I thought I'd share my experience that I had in the following years. The behavior seems to be that any property bindings will update correctly in cross-thread calls (no marshalling required; WPF will handle it for you). OTOH command bindings will need to be delegated to the UI dispatcher. I have tested it with both MVVM Light and the relatively new Community Toolkit and it seems to be the case with both the old Framework and the new .NET 5 and 6. AsyncRelayCommand fails to update the UI when invoked from non-UI thread (This happens when CanExecuteChanged is fired from a worker thread which updates, for example, button's Enabled property). The solution of course is to store UI dispatcher somewhere in the global space in your VM upon startup and then use it when updating the UI.
If you encounter this problem and UI Controls were created on a separate worker thread when working with BitmapSource or ImageSource in WPF, call Freeze() method first before passing the BitmapSource or ImageSource as a parameter to any method. Using Application.Current.Dispatcher.Invoke() does not work in such instances
this happened with me because I tried to access UI component in another thread insted of UI thread
like this
private void button_Click(object sender, RoutedEventArgs e)
{
new Thread(SyncProcces).Start();
}
private void SyncProcces()
{
string val1 = null, val2 = null;
//here is the problem
val1 = textBox1.Text;//access UI in another thread
val2 = textBox2.Text;//access UI in another thread
localStore = new LocalStore(val1);
remoteStore = new RemoteStore(val2);
}
to solve this problem, wrap any ui call inside what Candide mentioned above in his answer
private void SyncProcces()
{
string val1 = null, val2 = null;
this.Dispatcher.Invoke((Action)(() =>
{//this refer to form in WPF application
val1 = textBox.Text;
val2 = textBox_Copy.Text;
}));
localStore = new LocalStore(val1);
remoteStore = new RemoteStore(val2 );
}
You need to do it on the UI thread. Use:
Dispatcher.BeginInvoke(new Action(() => {GetGridData(null, 0)}));
For some reason Candide's answer didn't build. It was helpful, though, as it led me to find this, which worked perfectly:
System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke((Action)(() =>
{
//your code here...
}));
This works for me.
new Thread(() =>
{
Thread.CurrentThread.IsBackground = false;
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, (SendOrPostCallback)delegate {
//Your Code here.
}, null);
}).Start();
As mentioned here, Dispatcher.Invoke could freeze the UI. Should use Dispatcher.BeginInvoke instead.
Here is a handy extension class to simplify the checking and calling dispatcher invocation.
Sample usage: (call from WPF window)
this Dispatcher.InvokeIfRequired(new Action(() =>
{
logTextbox.AppendText(message);
logTextbox.ScrollToEnd();
}));
Extension class:
using System;
using System.Windows.Threading;
namespace WpfUtility
{
public static class DispatcherExtension
{
public static void InvokeIfRequired(this Dispatcher dispatcher, Action action)
{
if (dispatcher == null)
{
return;
}
if (!dispatcher.CheckAccess())
{
dispatcher.BeginInvoke(action, DispatcherPriority.ContextIdle);
return;
}
action();
}
}
}
I also found that System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke() is not always dispatcher of target control, just as dotNet wrote in his answer. I didn't had access to control's own dispatcher, so I used Application.Current.Dispatcher and it solved the problem.
The problem is that you are calling GetGridData from a background thread. This method accesses several WPF controls which are bound to the main thread. Any attempt to access them from a background thread will lead to this error.
In order to get back to the correct thread you should use SynchronizationContext.Current.Post. However in this particular case it seems like the majority of the work you are doing is UI based. Hence you would be creating a background thread just to go immediately back to the UI thread and do some work. You need to refactor your code a bit so that it can do the expensive work on the background thread and then post the new data to the UI thread afterwards
There are definitely different ways to do this depending on your needs.
One way I use a UI-updating thread (that's not the main UI thread) is to have the thread start a loop where the entire logical processing loop is invoked onto the UI thread.
Example:
public SomeFunction()
{
bool working = true;
Thread t = new Thread(() =>
{
// Don't put the working bool in here, otherwise it will
// belong to the new thread and not the main UI thread.
while (working)
{
Application.Current.Dispatcher.Invoke(() =>
{
// Put your entire logic code in here.
// All of this code will process on the main UI thread because
// of the Invoke.
// By doing it this way, you don't have to worry about Invoking individual
// elements as they are needed.
});
}
});
}
With this, code executes entirely on main UI thread. This can be a pro for amateur programmers that have difficulty wrapping their heads around cross-threaded operations. However, it can easily become a con with more complex UIs (especially if performing animations). Really, this is only to fake a system of updating the UI and then returning to handle any events that have fired in lieu of efficient cross-threading operations.
Also, another solution is ensuring your controls are created in UI thread, not by a background worker thread for example.
I kept getting the error when I added cascading comboboxes to my WPF application, and resolved the error by using this API:
using System.Windows.Data;
private readonly object _lock = new object();
private CustomObservableCollection<string> _myUiBoundProperty;
public CustomObservableCollection<string> MyUiBoundProperty
{
get { return _myUiBoundProperty; }
set
{
if (value == _myUiBoundProperty) return;
_myUiBoundProperty = value;
NotifyPropertyChanged(nameof(MyUiBoundProperty));
}
}
public MyViewModelCtor(INavigationService navigationService)
{
// Other code...
BindingOperations.EnableCollectionSynchronization(AvailableDefectSubCategories, _lock );
}
For details, please see https://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k(System.Windows.Data.BindingOperations.EnableCollectionSynchronization);k(TargetFrameworkMoniker-.NETFramework,Version%3Dv4.7);k(DevLang-csharp)&rd=true
Sometimes it can be the object you created that throws the exception, not the target where I was obviously looking at.
In my code here:
xaml file:
<Grid Margin="0,0,0,0" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" >
<TextBlock x:Name="tbScreenLog" VerticalAlignment="Stretch" Background="Black" FontSize="12" Foreground="#FF919191" HorizontalAlignment="Stretch"/>
</Grid>
xaml.cs file:
System.Windows.Documents.Run rnLine = new System.Windows.Documents.Run(Message.Item2 + "\r\n");
rnLine.Foreground = LineAlternate ? Brushes.Green : Brushes.Orange;
Dispatcher.Invoke(()=> {
tbScreenLog.Inlines.Add(rnLine);
});
LineAlternate = !LineAlternate;
I got the exception about accessing an object from a different thread but I was invoking it on the UI thread??
After a while it daunted on me that it was not about the TextBlock object but about the Run object I created before invoking.
Changing the code to this solved my problem:
Dispatcher.Invoke(()=> {
Run rnLine = new Run(Message.Item2 + "\r\n");
rnLine.Foreground = LineAlternate ? Brushes.Green : Brushes.Orange;
tbScreenLog.Inlines.Add(rnLine);
});
LineAlternate = !LineAlternate;
I encountered this error strangely on the second item selected from a WPF Control.
The reason was that I loaded the data into a RX SourceCache, and the loaded elements had ObservableCollections as Navigation Properties wrapped into a CollectionView. The ObservableCollections are connected to the UIThread and the data was loaded by the WorkerThread. As the CollectionView was only populated on displaying the first element, the issue with the different thread only occured on the second item being selected.
Solution would be to move the sublists to the ViewModel as ReadOnlyObservableCollections and filter the full list of the sub element table by the currently selected main element.
I'm facing some issues when I'm trying to deal with IBindingList inherited collections that are modified by a non-UI Thread and are at the same time bound to the DataSource Property of some Winforms Controls (e.g. an old fashion DataGridView).
My point is that Invoke / Begin are not suitable in my solution, cause the collections are not supposed to be really aware of the controls they are bound to.
Is there any other elegant solution than passing the UI Dispatcher or the appropriate SynchronizationContext reference to the collection which is going to be modified later on?
I was thinking about how one can detect whether that the current Thread is the UI accessing to the collection? If could know that,
therefore I could select to dispatch or not the collection modifications to the UI Thread. This solution still does not prevent to pass the reference of the UI SynchronizationContext or Dispatcher, though.
It would be nice to have real static properties which could give access, instead we have Dispatcher.CurrentDispatcher and WinformsSynchronizationContext that actually cannot be accessed from another thread... don't really get the point of the design if we still need to pass the reference from the UI Thread then. But I guess there is some reasons behind the scenes.
I may misunderstood and twist the definitions of some terms, so feel free to correct me if that's the case. I do not want to pretend that I'm expert or whatsoever.
The fact that delegating systematically everything to the UI seems a bit too much and might somehow overload the message pump cause in this scenario it would be better to create the collection right beforehand within the UI scope but then it would loose the interest of delegating the modifications among several threads.
I know that IBindingList is going to reflect through ListChanged (which is subscribed via the Control bound to a IBindingList inherited collection) the collection changes.
Basically those changes are going to be triggered by the non-UI Thread and that basically the very bottom line. Although, I'm bit frustrated by the lack of elegant solutions.
By the way, I am not willing to use the async/await and neither some special events of the BackgroundWorker that automatically Marhsall to the UI Thread when they are triggered. Maybe I should have a look at http://referencesource.microsoft.com/
Please find below the results of my investigations about the UI Marshalling:
public static class Program
{
[STAThread]
public static void Main(params String[] arguments)
{
var bindingList = new BindingList<Record>();
Action<Object> action = (argument) =>
{
var synchronizationContext = argument as SynchronizationContext;
var dispatcher = argument as Dispatcher;
if ((synchronizationContext == null) && (dispatcher == null))
{
throw new ArgumentException(#"argument");
}
else
{
var random = new Random();
while (true)
{
Action<Object> sendOrPostCallbackAction = (state) =>
{
var record = new Record();
bindingList.Add(record);
};
var sendOrPostCallback = new SendOrPostCallback(sendOrPostCallbackAction);
// WindowsFormsSynchronizationContext.Current.Send(sendOrPostCallback, new Object());
// *** NullReferenceException
// *** Object reference not set to an instance of an object...
// *** Thought this could be found not matter where.
// *** Cause the winforms application is already started at that point in the code.
// Dispatcher.CurrentDispatcher.Invoke(sendOrPostCallback, (Object)null);
// *** InvalidOperationException
// *** Cross-thread operation not valid: Control '' accessed from a thread other than the thread it was created on.
if (dispatcher != null)
{
// Only Works if the synchronizationContext is the one used by the UI Thread...
dispatcher.Invoke(sendOrPostCallback, (Object)null);
}
else
{
// Only Works if the synchronizationContext is the one used by the UI Thread...
synchronizationContext.Send(sendOrPostCallback, (Object)null);
}
Thread.Sleep(100);
}
}
};
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var mainForm = new MainForm
{
DataGridView = { DataSource = bindingList },
};
mainForm.ButtonPassSynchronizationContext.Click += (sender, args) =>
{
mainForm.ButtonPassSynchronizationContext.Enabled = false;
mainForm.ButtonPassDispatcher.Enabled = false;
// Of course here the synchronization context is the one used by the UI Thread...
// WindowsFormsSynchronizationContext.Current should be equal here.
var areTheSameSyncrhonizationContexts = SynchronizationContext.Current == WindowsFormsSynchronizationContext.Current;
Debug.WriteLineIf(areTheSameSyncrhonizationContexts, #"The sychronization contexts are the same.");
Task.Factory.StartNew(action, SynchronizationContext.Current);
};
mainForm.ButtonPassDispatcher.Click += (sender, args) =>
{
mainForm.ButtonPassSynchronizationContext.Enabled = false;
mainForm.ButtonPassDispatcher.Enabled = false;
// The dispatcher is indeed not null, since it is the one used by the UI Thread...
// Dispatcher.CurrentDispatcher should be not null here.
var isDispatcherNotNull = Dispatcher.CurrentDispatcher != null;
Debug.WriteLineIf(isDispatcherNotNull, #"The Current Dispatcher is not null.");
Task.Factory.StartNew(action, Dispatcher.CurrentDispatcher);
};
Application.Run(mainForm);
}
public class Record
{
public Record()
{
var random = new Random();
this._dummyProperty = random.Next(0, 100);
}
private readonly Int32 _dummyProperty;
public Int32 DummyProperty
{
get { return this._dummyProperty; }
}
}
public class MainForm : Form
{
public MainForm()
{
this.InitializeComponent();
}
public Button ButtonPassSynchronizationContext { get; private set; }
public Button ButtonPassDispatcher { get; private set; }
public DataGridView DataGridView { get; private set; }
private void InitializeComponent()
{
this.Text = #"Main Form";
this.StartPosition = FormStartPosition.CenterScreen;
this.DataGridView = new DataGridView
{
Dock = DockStyle.Fill,
AllowUserToAddRows = false,
AllowUserToDeleteRows = false,
DefaultCellStyle = { Alignment = DataGridViewContentAlignment.MiddleCenter },
AlternatingRowsDefaultCellStyle = { BackColor = Color.LightGoldenrodYellow },
};
this.ButtonPassSynchronizationContext = new Button()
{
Dock = DockStyle.Bottom,
Text = #"Pass SynchronizationContext",
};
this.ButtonPassDispatcher = new Button()
{
Dock = DockStyle.Bottom,
Text = #"Pass Dispatcher",
};
this.Controls.Add(this.DataGridView);
this.Controls.Add(this.ButtonPassSynchronizationContext);
this.Controls.Add(this.ButtonPassDispatcher);
}
}
}
This code is running from other thread than the thread it was created on.
Thread gets create from the constructor of StartScanning
public StartScanning()
{
InitializeComponent();
Thread _IMSS_THREAD = new Thread(_IMSS_START_SCANNING);
_IMSS_THREAD.IsBackground = true;
_IMSS_THREAD.Start();
}
Main form
StartScanning _IMSS_START_SCANNING = StartScanning._IMSS_CREATE_CONTROLE();
_IMSS_START_SCANNING._IMSS_ON_ALL_SCAN_COMPLETE += _IMSS_ON_SCAN_COMPLETE;
this._IMSS_MainPanel.Controls.Add(_IMSS_START_SCANNING);
On scan complete user control, this code is in main form:
ScanComplete _IMSS_ON_COMPLETE = new ScanComplete();
public void _IMSS_ON_SCAN_COMPLETE(ref List<BetterListViewGroup> _IMSS_LIST_OF_GROUP_TARGETS)
{
List<BetterListViewGroup> IMSS_LIST_OF_GROUP_TARGETS = _IMSS_LIST_OF_GROUP_TARGETS;
_IMSS_ON_COMPLETE._IMSS_AddRangeTargets(ref IMSS_LIST_OF_GROUP_TARGETS);
this.Invoke(new MethodInvoker(() =>
{
this._IMSS_MainPanel.Controls.Clear();
this._IMSS_MainPanel.Controls.Add(_IMSS_ON_COMPLETE);
}));
}
If you take a look on this code, it runs OK but it's supposed to throw
Cross-thread operation not valid, cause when we start the program this UserControl
ScanComplete _IMSS_ON_COMPLETE = new ScanComplete();
Get created on the main thread (it's global) and when we use
_IMSS_ON_COMPLETE._IMSS_AddRangeTargets(ref IMSS_LIST_OF_GROUP_TARGETS);
It adds a list of groups of listview to it, and it's out of the invoke section, but it's not throwing thread error, Why it's not throwing errors?
Try this in constructor of form:
public StartScanning()
{
InitializeComponent();
StartScanning.CheckForIllegalCrossThreadCalls = false;
}
Remember that this is not proper way to do this but this will help you to solve your problem. Search for Thread safe calling.
I'm having trouble with the concept of threads and how to use them.
I'm trying to code a fairly basic chat program (as part of a larger program) and it currently works like this:
The 'NetworkSession' class receives the input from the server on a separate thread in a loop. If it receives input that indicates it should open a new chat window it constructs a new WPF class (ChatWindow) and displays it.
Originally I got the error that "The calling thread must be STA, because many UI components require this.". So i set the thread to be STA but now of course the WPF form is unusable because its running on the same thread as the blocking loop.
So my question is how do I create a new instance of a WPF form from within another thread.
I've seen alot of discussion about this but it tends to deal with running a delegate from a form that has already been constructed.
Here is some code.
while (Connected) //this loop is running on its own thread
{
Resp = srReceiver.ReadLine();
if (Resp.StartsWith("PING")) SendToServer("PONG");
if (Resp.StartsWith("CHAT FROM"))
{
String[] split = Resp.Split(' ');
Console.WriteLine("Incoming Chat from {0}", split[2]);
bool found = false;
if (Chats.Count != 0)
{
foreach (ChatWindow cw in Chats)
{
if (cw.User == split[2])
{
found = true;
cw.AddLine(cw.User, split[3]); // a function that adds a line to the current chat
}
}
}
if (!found)
{
ChatWindow temp = new ChatWindow(split[2], split[3]);
Chats.Add(temp); //this is a collection with T = ChatWindow
temp.Show();
}
}
}
If you're constructing NetworkSession from your UI Thread, you can snag a reference to the current Dispatcher that can manipulate the UI later.
NetworkSession.cs
private Dispatcher _dispatcher;
public NetworkSession()
{
_dispatcher = Dispatcher.CurrentDispatcher;
}
//any thread can call this method
public void DoStuff()
{
Action action = () =>
{
ChatWindow temp = new ChatWindow(split[2], split[3]);
Chats.Add(temp);
temp.Show();
};
_dispatcher.BeginInvoke(action);
}
The code below, which I took from here worked for me:
public static void StartChatWindow()
{
Thread thread = new Thread(() =>
{
ChatWindow chatWindow = new ChatWindow();
chatWindow.Chat(); // Do your stuff here, may pass some parameters
chatWindow.Closed += (sender2, e2) =>
// Close the message pump when the window closed
chatWindow.Dispatcher.InvokeShutdown();
// Run the message pump
System.Windows.Threading.Dispatcher.Run();
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
}
What you really need to do is construct the window/form on your main UI thread. You probably need to define a delegate that you can call from your network thread and that delegate should have a method attached that will call this.Dispatcher.BeginInvoke() -> inside which you will construct your window.
The this.Dispatcher.BeginInvoke() call is necessary to execute code on the UI thread, otherwise even with a delegate, code would be executed on the network thread.
Both the delegate and the method for creating a new chat window should probably be attached to the MainWindow...