Reputation:
I am using async
and await
keywords to make an app responsive
I have the following code in the View
when the property is raised in the ViewModel
.
VIEW: Button Click Event
private async void button1_Click(object sender, RoutedEventArgs e)
{
button1.IsEnabled = false;
// Property Change
_viewModel.Words = await Task.Factory.StartNew(() => File.ReadAllLines("Words.txt").ToList()); // takes time to read about 3 - 4 seconds
switch (_viewModel.RadioButtonWordOrderSelection)
{
case MainWindowViewModel.RadioButtonWordOrderSelections.NormalOrder:
break;
case MainWindowViewModel.RadioButtonWordOrderSelections.ReverseOrder:
await Task.Factory.StartNew(() =>
{
var words = _viewModel.Words.ToList();
words.Reverse();
// Property Change
_viewModel.Words = words;
});
break;
case MainWindowViewModel.RadioButtonWordOrderSelections.Shuffle:
await Task.Factory.StartNew(() =>
{
// Property Change
_viewModel.Words = _viewModel.Words.Shuffle().ToList();
});
break;
}
await Task.Factory.StartNew(() => DownloadSomething(_viewModel.Words)); // takes time to read about 30 - 40 seconds
button1.IsEnabled = true;
}
_viewModel.Progress
gets updated in the View
VIEW: Private Method Takes 30 - 40 seconds To Complete
private void DownloadSomething(IEnumerable<string> words)
{
// Property Change
_viewModel.Progress = 0;
foreach (var word in words)
{
// Property Change
_viewModel.Word = word;
try
{
// Some code WebClient download code here
}
catch (Exception e)
{
//Trace.WriteLine(e.Message);
}
// Property Change
_viewModel.Progress++;
}
}
VIEW: Property Change Event Handled
void _viewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
try
{
switch(e.PropertyName)
{
case "Progress":
// Since its a different Thread
// http://msdn.microsoft.com/en-us/magazine/cc163328.aspx
// Sets the Value on a ProgressBar Control.
// This will work as its using the dispatcher
// The following works
//Dispatcher.Invoke(
// DispatcherPriority.Normal,
// new Action<double>(SetProgressValue),
// _viewModel.Progress);
// from http://stackoverflow.com/questions/2982498/wpf-dispatcher-the-calling-thread-cannot-access-this-object-because-a-differen
progress1.Dispatcher.Invoke(
DispatcherPriority.Normal,
new Action(() =>
{
progress1.Value = _viewModel.Progress;
})
);
// This will throw an exception
// (it's on the wrong thread)
//progress1.Value = _viewModel.Progress;
break;
case "Words":
// Since its a different Thread
// http://msdn.microsoft.com/en-us/magazine/cc163328.aspx
// Sets the Max Value on a ProgressBar Control.
// This will work as its using the dispatcher
// The following Works
//Dispatcher.Invoke(
// DispatcherPriority.Normal,
// new Action<double>(SetProgressMaxValue),
// _viewModel.Words.Count);
// from http://stackoverflow.com/questions/2982498/wpf-dispatcher-the-calling-thread-cannot-access-this-object-because-a-differen
progress1.Dispatcher.Invoke(
DispatcherPriority.Normal,
new Action(() =>
{
progress1.Maximum = _viewModel.Words.Count;
})
);
// This will throw an exception
// (it's on the wrong thread)
//progress1.Maximum = _viewModel.Words.Count;
break;
case "Word":
// Since its a different Thread
// http://msdn.microsoft.com/en-us/magazine/cc163328.aspx
// Sets the Contant on a Label Control.
// This will work as its using the dispatcher
// The following Works
//Dispatcher.Invoke(
// DispatcherPriority.Normal,
// new Action<string>(SetLastWordValue),
// _viewModel.Word);
// from http://stackoverflow.com/questions/2982498/wpf-dispatcher-the-calling-thread-cannot-access-this-object-because-a-differen
labelLastWord.Dispatcher.Invoke(
DispatcherPriority.Normal,
new Action(() =>
{
labelLastWord.Content = _viewModel.Word;
})
);
// This will throw an exception
// (it's on the wrong thread)
//labelLastWord.Content = _viewModel.Word;
break;
case "RadioButtonWordOrderSelection":
break;
default:
throw new NotImplementedException("[Not implemented for 'Property Name': " + e.PropertyName + "]");
}
}
catch(Exception ex)
{
MessageBox.Show(ex.Message + "\n" + ex.StackTrace);
}
}
The UI updates perfectly for the progressBar1 and labelLastWord! I am facing a problem though, when progressBar1 and labelLastWord is updating the rest of the UI is frozen.
Is there a work around to fix this?
I greatly appreciate any help!
Upvotes: 0
Views: 441
Reputation: 456447
I strongly recommend you follow the guidelines in the Task-based Async Programming document. This is far better than just shunting work off to the thread pool via StartNew
. If you do have CPU-bound operations, you could use Task.Run
, but for everything else, use the existing async
-ready endpoints.
Also, I find it useful to treat the entire VM as being in the UI context. So PropertyChanged
is always raised in a UI context. Data binding in particular depends on this.
private async Task<List<string>> ReadAllLinesAsync(string file)
{
var ret = new List<string>();
using (var reader = new StreamReader(file))
{
string str = await reader.ReadLineAsync();
while (str != null)
{
ret.Add(str);
str = await reader.ReadLineAsync();
}
}
return ret;
}
private async void button1_Click(object sender, RoutedEventArgs e)
{
button1.IsEnabled = false;
_viewModel.Words = await ReadAllLinesAsync("Words.txt");
List<string> words;
switch (_viewModel.RadioButtonWordOrderSelection)
{
case MainWindowViewModel.RadioButtonWordOrderSelections.NormalOrder:
break;
case MainWindowViewModel.RadioButtonWordOrderSelections.ReverseOrder:
await Task.Run(() =>
{
words = _viewModel.Words.ToList();
words.Reverse();
});
_viewModel.Words = words;
break;
case MainWindowViewModel.RadioButtonWordOrderSelections.Shuffle:
await Task.Run(() =>
{
words = _viewModel.Words.Shuffle().ToList();
});
_viewModel.Words = words;
break;
}
await DownloadSomething(_viewModel.Words);
button1.IsEnabled = true;
}
private async Task DownloadSomething(IEnumerable<string> words)
{
_viewModel.Progress = 0;
foreach (var word in words)
{
_viewModel.Word = word;
try
{
await ...; // async WebClient/HttpClient code here
}
catch (Exception e)
{
//Trace.WriteLine(e.Message);
}
_viewModel.Progress++;
}
}
void _viewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
try
{
switch(e.PropertyName)
{
case "Progress":
progress1.Value = _viewModel.Progress;
break;
case "Words":
progress1.Maximum = _viewModel.Words.Count;
break;
case "Word":
labelLastWord.Content = _viewModel.Word;
break;
case "RadioButtonWordOrderSelection":
break;
default:
throw new NotImplementedException("[Not implemented for 'Property Name': " + e.PropertyName + "]");
}
}
catch(Exception ex)
{
MessageBox.Show(ex.Message + "\n" + ex.StackTrace);
}
}
As a closing note, I recommend you purchase and read thoroughly Josh Smith's MVVM book. You're using terms like "View" and "ViewModel", but the way you're using these components, you're completely avoiding all the advantages of the MVVM pattern.
Upvotes: 1