Reputation: 169
I am trying to figure out how to update my main UI (for example a textblock) from another thread. The only way I have been able to do it so far is using the Progress object. I have a situation where I cannot use the Progress object and I was recently pointed to using the MVVM / binding method. I have watch a few videos and done some examples but I can't seem to get it to work.
<TextBlock Name="txtblock1" Text="{Binding count}"></TextBlock>
Here are my errors
Exception thrown: 'System.Runtime.InteropServices.COMException' in System.Runtime.WindowsRuntime.dll Exception thrown: 'System.Runtime.InteropServices.COMException' in mscorlib.ni.dll Exception thrown: 'System.Runtime.InteropServices.COMException' in mscorlib.ni.dll
View (Code behind)
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
ViewModelexample obj = new ViewModelexample();
txtblock1.DataContext = obj;
obj.Methodasync();
}
}
ViewModel
public class ViewModelexample : INotifyPropertyChanged
{
public string count { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected void onPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler !=null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public async void Methodasync()
{
await Method();
}
public Task Method()
{
return Task.Factory.StartNew(() =>
{
for (int i = 0; i < 100; i++)
{
Task.Delay(1000).Wait();
Debug.WriteLine(i.ToString());
count = i.ToString();
onPropertyChanged(i.ToString());
}
});
}
}
Any ideas on what I might be doing wrong?
Thank you
Upvotes: 1
Views: 473
Reputation: 457472
I was under the assumption that binding would take care of the cross threading call.
No, this is only true for some MVVM frameworks (such as WPF). For this reason, I prefer to treat all my ViewModels as belonging to the UI.
With your code sample, you should be able to use Progress<T>
:
public async void Methodasync()
{
var progress = new Progress<int>(value =>
{
count = value;
onPropertyChanged("count");
});
await Method(progress);
}
public Task Method(IProgress<int> progress)
{
return Task.Run(() =>
{
for (int i = 0; i < 100; i++)
{
Task.Delay(1000).Wait();
Debug.WriteLine(i.ToString());
progress.Report(i);
}
});
}
Also note that I changed from StartNew
to Run
. As a general rule, don't use StartNew
for reasons I describe on my blog.
If you "really for serious" cannot use Progress<T>
for some odd reason, then you can use Reactive Extensions or SynchronizationContext
directly.
Example using SynchronizationContext
:
public Task Method()
{
var context = SynchronizationContext.Current;
return Task.Run(() =>
{
for (int i = 0; i < 100; i++)
{
Task.Delay(1000).Wait();
Debug.WriteLine(i.ToString());
var localI = i;
context.Post(() =>
{
count = localI;
onPropertyChanged("count");
});
}
});
}
The localI
thing is to avoid closing over the loop variable.
Upvotes: 2
Reputation: 247631
Your count
Property needs to be able to notify that it has changed
public class ViewModelexample : INotifyPropertyChanged
{
private string _count;
public string count {
get { return _count; }
set {
if(value != _count) {
_count = value;
OnPropertyChanged(nameof(count));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
// the new Null-conditional Operators are thread-safe:
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private int _testCount = 0;
public void Method() {
testCount++;
Debug.WriteLine(testCount.ToString());
count = testCount.ToString();
}
}
The above works because the new Null-conditional Operators are thread-safe:
Another use for the null-condition member access is invoking delegates in a thread-safe way with much less code. ... The new way is thread-safe because the compiler generates code to evaluate PropertyChanged one time only, keeping the result in a temporary variable.
To Test it you can edit your ViewModel method and have the view call it on an event like on page loaded or a button click.
public sealed partial class MainPage : Page
{
ViewModelexample obj = null;
public MainPage()
{
this.InitializeComponent();
obj = new ViewModelexample();
this.DataContext = obj;
}
public void OnSomeEventHandler() {
obj.Method();
}
}
Upvotes: 3