Reputation: 14370
I have the following code running in a WPF app:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(MainWindow_Loaded);
}
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
object obj = new object();
Collection.Add(obj);
Collection.CollectionChanged += new NotifyCollectionChangedEventHandler(delegate(object sender2, NotifyCollectionChangedEventArgs e2)
{
if (Collection.Count == 0)
App.Current.MainWindow.Close();
});
Task.Factory.StartNew(() =>
{
//Do long running process
Collection.Remove(obj); //this errors out
});
}
private ObservableCollection<object> Collection = new ObservableCollection<object>();
}
I get the error System.InvalidOperationException
: The calling thread cannot access this object because a different thread owns it.
I was under the impression that Task.Factory.StartNew
queued up an async task, so the thread should be the same, no?
Upvotes: 1
Views: 464
Reputation: 14370
Just to add to Arthur's answer, in my real application (not the sample code above) I needed to do this from an MvvmLight view model. To access the dispatcher from a ViewModel:
Inside App, add the following:
static App()
{
DispatcherHelper.Initialize();
}
And then instead of calling this.Dispatcher
, because a ViewModel has no reference to the Dispatcher, the following will work:
DispatcherHelper.UIDispatcher.BeginInvoke((Action)(() => App.Current.MainWindow.Close()));
Upvotes: 1
Reputation: 7058
Task.Factory.StartNew executes your action in the default TaskScheduler, so it will run in the ThreadPool.
ObservableCollection is not thread-safe. It means that your CollectionChanged handler, which performs operations on UI controls ( App.Current.MainWindow.Close() ) is not going to be executed in the UI thread because the collection modification is being done in your Task's action, causing the error you are seeing.
If you only need to interact with the UI in your handler, you can use the dispatcher:
Collection.CollectionChanged += new NotifyCollectionChangedEventHandler(delegate(object sender2, NotifyCollectionChangedEventArgs e2)
{
if (Collection.Count == 0)
this.Dispatcher.BeginInvoke((Action)(()=> App.Current.MainWindow.Close()));
});
If you need to bind to it, consider using a thread-safe implementation. See this.
Upvotes: 3