Get value from threaded method - c#

I'm trying to access a database, get an image and return it so it then can be shown in a Border object. (So I'm converting the byte[] data to an image, too)
This process takes enough time to notice that the UI has frozen.
Here's some code:
ImageBrush imgb = new ImageBrush();
imgb.ImageSource = GlobalDB.GetImage(album.name, 400); // Size is 400px, this is the time-consuming part.
AlbumArt.Background = imgb;
I tried using a backgroundworker but that gave me an exception saying I can't call from different threads. I got that exception since the BackgroundWorker.RunWorkerCompleted apparently was owned by a different thread(?)
Adding to that last bit: the Runworkercompleted did this:
AlbumArt.Background = imgb;
I now don't know what to do.
Any suggestions will be appreciated!

RunWorkerCompleted gets called on UI thread only.
Only issue i can see in your code is imgb is created in background thread and WPF has constraint that DependencyProperty source and Dependency object should be created on same thread.
So before assigning imgb to Background call Freeze() on it:
imgb.Freeze();
AlbumArt.Background = imgb;
Reason being freezed objects are allowed to be access across threads. They are not thread affine.

I have to ask. Where is your view model? Rohit Vats's answer will more or less get the job done, but you are not approaching this idiomatically. You should have a ViewModel with code something like this:
public class AlbumViewModel: BaseViewModel // BaseViewModel is your code that implements INotifyPropertyChanged
{
private string name
public string Name
{
get{ return name;}
set
{
if(name != value)
{
name = value;
FirePropertyChanged("Name");
LoadBackgroundImageAsync(value);
}
}
}
private ImageSource backgroundImage;
public ImageSource BackgroundImage
{
get{return backgroundImage;}
private set
{
if(backgroundImage != value)
{
background = value;
FirePropertyChanged("BackgroundImage");
}
}
}
private Task LoadBackgroundImageAsync(string name)
{
var retv = new Task(()=>
{
var source = GlobalDB.GetImage(name, 400);
source.Freeze();
BackgroundImage = source;
});
retv.Start();
return retv;
}
}
Then just bind to the property and let WPF wory about updating the UI.

not sure about what GetImage are doing, but maybe you can enclosure your code into a Task:
Task.Factory.StartNew(() => {
ImageBrush imgb = new ImageBrush();
imgb.ImageSource = GlobalDB.GetImage(album.name, 400);
AlbumArt.Background = imgb;
});

As already suggested in another answer, you may alternatively use an asynchronous task. But you have to dispatch the assignment of the Background property to the UI thread after freezing the ImageBrush.
Task.Factory.StartNew(() =>
{
var imgb = new ImageBrush(GlobalDB.GetImage(album.name, 400));
imgb.Freeze();
Dispatcher.BeginInvoke(new Action(() => AlbumArt.Background = imgb));
});

Related

System.InvalidOperationException: 'The calling thread cannot access this object because the object is owned by another thread.' [duplicate]

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.

System.InvalidOperationException when creating new instance of window in WPF [duplicate]

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.

How to correctly refresh data in a grid using Task in WPF

My small WPF code is giving me this error
The calling thread cannot access this object because a different thread owns it
I know what exactly it is saying but I am unable to understand how can I fix it. I have tried different things but no luck and I admit that I am not good in Task library.
This is what I am trying to achieve.
Load data when WPF form loads - This is ok
On the form user will press the Refresh button to refresh the data from the database.
here is my code
public partial class DocListView : UserControlListBase
{
private ScecoBillDataScope _scecoBillDataScope;
public EntityCollection<ScecoBillEntity> ScecoBills = new EntityCollection<ScecoBillEntity>();
public DocListView()
{
InitializeComponent();
LoadData();
}
private async void LoadData()
{
await Task.Run(() =>
{
_scecoBillDataScope.FetchData();
});
var collectionView = CollectionViewSource.GetDefaultView(_scecoBillDataScope.ScecoBills);
await Dispatcher.BeginInvoke(new ThreadStart(()=> LayoutRoot.DataContext = collectionView));
}
private void BbiRefresh_ItemClick(object sender, DevExpress.Xpf.Bars.ItemClickEventArgs e)
{
_scecoBillDataScope.Reset();
LoadData();
e.Handled = true;
}}
Actually the error is appearing when I click the Refresh button at this line _scecoBillDataScope.FetchData();
Please advice.
Your problem is this line:
await Dispatcher.BeginInvoke(new ThreadStart(()=> LayoutRoot.DataContext = collectionView));
You're just creating a new thread, you have to actually dispatch on the GUI thread:
Application.Current.Dispatcher.Invoke(() =>
{
LayoutRoot.DataContext = collectionView;
});
Since your collection items are data bound to your view, you have to treat them as part of your UI.
Try loading a new collection from within Task.Run, and then copying them over your existing items while on the UI thread. So, the new collection is built on a thread pool thread (Task.Run), and then the data-bound collection items are modified from the UI thread.
It looks like you want to do something in UI Thread.
As You know You can use Dispatcher class.
But There is another way to ask something to UI Thread.
var uiThread = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(() =>
{
// I assumed that It returns boolean value
_scecoBillDataScope.FetchData();
}).ContinueWith(x =>
{
// Here you can put the code to work on the UI thread.
if (x.Result)
{
var collectionView = CollectionViewSource.GetDefaultView(_scecoBillDataScope.ScecoBills);
LayoutRoot.DataContext = collectionView;
}
}, uiThread);
I hope it helps.
thank you.

Winforms: How to dispatch properly collection changes when bound to UI Controls?

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

DrawingGroup - crossthreading

I have a class that have two fields and some methods for drawing:
public class DrawingTool
{
private readonly DrawingGroup _drawingGroup;
private DrawingImage _imageSource;
public DrawingTool()
{
_drawingGroup = new DrawingGroup();
_imageSource = new DrawingImage(_drawingGroup);
}
public DrawingImage GetImageSource()
{
return _imageSource;
}
public void DrawSomething()
{
using (DrawingContext dc = _drawingGroup.Open()) // HERE'S AN ERROR - _drawing group is being used by another thread (InvalidOperationException)
{
// draw something here
}
}
}
This is a WPF project, so I also have an Image:
<Image x:Name="MyImage" Width="320" Height="240"></Image>
I set the source:
_drawingTool = new DrawingTool();
Dispatcher.Invoke(() => MyImage.Source = _drawingTool.GetImageSource());
_device.FrameReady += (sender, args) => Dispatcher.Invoke(() =>
{
_drawingTool.DrawSomething();
});
I'm getting an error from the comment... I tried to invoke DrawSomething() method and use CheckAccess() for _drawingGroup inside DrawSomething() - didn't help. How to make it possible to work?
Thanks for help in advance!
[edit 1.]
Maybe I should use something different than DrawingGroup?
[edit 2.]
No one has an idea? :(
Ok, I found the solution... It was really obvious... I had to initialize whole drawing tool in the main thread (in the main window constructor), then set MyImage.Source (also in the same place). Now everything's gonna work! :)

Categories