I am working on a WPF Prism application. I have a DelgateCommand that is responsible for populating an ObservableCollection which is owned by the UI thread asynchronously using async and await. The collection in turn is bound to a chart.
In order to enable the collection to be accessed by multiple threads, I have enabled the synchronization which is as follows:
BindingOperations.EnableCollectionSynchronization(ChartBoundCollection, _lock);
The command handler logic is as as follows:
private async void ShowPatientVisitsVsDays()
{
IsChartBeingPopulated = true;
this.ChartSubTitle = "Requests Vs Days";
this.SeriesTitle = "Days";
ChartBoundCollection.Clear();
await ShowRequestsVsDaysAsync();
IsChartBeingPopulated = false;
}
The Logic which populates the observable collection is as follows:
private async Task ShowRequestsVsDaysAsync()
{
await Task.Run(() =>
{
if (PatientVisits.Count() > 0)
{
var days = PatientVisits.Select(p => p.VisitDate.Value.Date).Distinct();
foreach (var i in days)
{
var dayVisitCount = PatientVisits.Where(p => p.VisitDate.Value.Date == i).Count();
ChartBoundCollection.Add(new PatientVisitVsXAxis() { XAxis = i.ToShortDateString(), NumberOfPatientVisits = dayVisitCount });
}
}
});
}
The issue that I am facing is that the continuation where I am setting IsChartBeingPopulated = false is not getting executed after the asynchronous method on which the await is set is completed.
await ShowRequestsVsDaysAsync();
Thus IsChartBeingPopulated is set even before the asynchronous method
is completed.
the command handler ShowPatientVisitsVsDays() is invoked by the click
of the button on the View. The button is bound to the following
command:
ShowPatientVisitsVsDaysCommand = new DelegateCommand(ShowPatientVisitsVsDays);
IsChartBeingPopulated is being used to set the IsBusy DependencyProperty of the BusyIndiator control belonging to the 'Extended WPF ToolKit'.
The idea is to show the BusyIndicator while the chart data is being populated in the bound collection.
<xctk:BusyIndicator IsBusy="{Binding Path=IsChartBeingPopulated}" >
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<chart:ClusteredColumnChart Grid.Row="0" ChartTitle="Patient Visits History" ChartSubTitle="{Binding Path=ChartSubTitle}">
<chart:ClusteredColumnChart.Series>
<chart:ChartSeries SeriesTitle="{Binding Path=SeriesTitle}" DisplayMember="XAxis" ValueMember="NumberOfPatientVisits" ItemsSource="{Binding Path=ChartBoundCollection}" />
</chart:ClusteredColumnChart.Series>
</chart:ClusteredColumnChart>
</Grid>
</xctk:BusyIndicator>
Not sure what the issue is. Does someone has any idea what is causing this?
Your are not synchronizing the access to the collection. BindingOperations.EnableCollectionSynchronization does not magically make collection thread safe. It only ensures that databinding engine does not enumerate the collection without taking the lock.
You still need to take the lock on _lock object when adding and clearing collection.
See here for more info on EnableCollectionSynchronization.
You can do something like this;
private async void ShowPatientVisitsVsDays()
{
IsChartBeingPopulated = true;
this.ChartSubTitle = "Requests Vs Days";
this.SeriesTitle = "Days";
new ChartBoundCollection().Clear();
IsChartBeingPopulated = await ShowRequestsVsDaysAsync();//here we are waiting till the async method is finished.
}
private async Task<bool> ShowRequestsVsDaysAsync()
{
return await Task.Run(() =>
{
if (PatientVisits.Any())//replace Count with Any to avoid unwanted enumerations.
{
var days = PatientVisits.Select(p => p.VisitDate.Value.Date).Distinct();
foreach (var i in days)
{
var dayVisitCount = PatientVisits.Count(p => p.VisitDate.Value.Date == i);
chartBoundCollection.Add(new PatientVisitVsXAxis() { XAxis = i.ToShortDateString(), NumberOfPatientVisits = dayVisitCount });
}
}
Thread.Sleep(5000);//this is for testing. Sleep the thread for 5secs. Now your busyIndicator must be visible for 5secs minimum.
return false;//return false, so that we can use it to populate IsChartBeingPopulated
});
}
UPDATE: I think you have some doubts about async await. Below code will help you to clear them up.
Create a new console app and place this code in Program class.
As per your comment below, the text in ShowPatientVisitsVsDays should get printed before it prints anything from the 'ShowRequestsVsDaysAsync' method. Does it work that way? Test this and let us know.
static void Main(string[] args)
{
Console.WriteLine("Main started");
ShowPatientVisitsVsDays();
Console.ReadLine();
}
private static async void ShowPatientVisitsVsDays()
{
await ShowRequestsVsDaysAsync();
Console.WriteLine("ShowPatientVisitsVsDays() method is going to SLEEP");
Thread.Sleep(2000);
Console.WriteLine("ShowPatientVisitsVsDays() method finished");
}
private static async Task ShowRequestsVsDaysAsync()
{
await Task.Run(() =>
{
Console.WriteLine("ASYNC ShowRequestsVsDaysAsync() is going to SLEEP.");
Thread.Sleep(5000);
Console.WriteLine("ASYNC ShowRequestsVsDaysAsync finished.");
});
}
}
Related
I have been googling this for quite a few hours, and read quite a few SO questions where this is discussed but I am sorry to say I just don't get how to use it.
Basically what I am trying to do is to have a label in a WPF/Win Forms app display the following while an async task is running:
Processing .
and at each 1 second interval to add another dot until I get to three and then start over at 1 until the task is done.
As a first step I am only trying to add a dot after each second and have tried it with an IProgress action but the only thing that I have been able to accomplish is either nothing or the label gets populated with dots in one shot and the other task seems to run after that is done.
I next tried doing the following:
private async void startButton_Click(object sender, RoutedEventArgs e)
{
resultsTextBox.Text = "Waiting for the response ...";
startButton.IsEnabled = false;
resultsTextBox.Clear();
var task = SumPageSizesAsync();
var progress = Task.Run(() =>
{
var aTimer = new System.Timers.Timer(1000);
aTimer.Elapsed += OnTimedEvent;
aTimer.AutoReset = true;
aTimer.Enabled = true;
void OnTimedEvent(object source, ElapsedEventArgs et)
{
if (!lblProgress.Dispatcher.CheckAccess())
{
Dispatcher.Invoke(() =>
{
lblProgress.Content += ".";
});
}
}
});
await task;
await progress;
resultsTextBox.Text += "\r\nControl returned to startButton_Click.";
startButton.IsEnabled = true;
}
But again the label just gets populated with dots at once while the other task keeps running.
I took this example from the Microsoft Docs
UPDATE:
I have now tried removing the loop while(!task.IsComplete) which basically makes the label start to be updated after the first task has finished. Then I tried to the following:
var task = SumPageSizesAsync();
var progress = GetProgress();
await Task.WhenAll(SumPageSizesAsync(), GetProgress());
But got the same result, the label begins to update after the first task has concluded.
Thank you for your help.
"Progress(T)" is the wrong pattern for this.
Here is the code for a WPF application that does this with 100% async / await code, no additional threads are created.
It starts two async tasks. The first simulates the long running async process. The second one starts another async Task that takes the first task as a parameter. It loops until the first task is completed, while updating a label with a "..." pattern. It awaits a Task.Delay to control the animation speed.
Both those tasks are placed in to a list, and the we await the completion of both of them.
This could all be wrapped up in in to a ShowProgressUntilTaskCompletes method (or extension method) that takes the worker Task as a parameter, which gives you an easily reusable method of showing a progress indicator for any Task.
MainWindow.xaml:
<Window
x:Class="LongProcessDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="800"
Height="450"
mc:Ignorable="d">
<StackPanel Margin="100" Orientation="Vertical">
<Button Click="StartProcess_OnClick" Content="Start" />
<TextBlock
Name="LoadingText"
Padding="20"
Text="Not Running"
TextAlignment="Center" />
</StackPanel>
</Window>
MainWindow.xaml.cs:
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows;
namespace LongProcessDemo
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private async void StartProcess_OnClick(object sender, RoutedEventArgs e)
{
var longRunningTask = SimulateLongRunningTask();
var spinner = ShowSpinner(longRunningTask);
var tasks = new List<Task>
{
longRunningTask,
spinner,
};
await Task.WhenAll(tasks);
}
private async Task ShowSpinner(Task longRunningTask)
{
var numDots = 0;
while (!longRunningTask.IsCompleted)
{
if (numDots++ > 3) numDots = 0;
LoadingText.Text = $"Waiting{new string('.', numDots)}";
await Task.Delay(TimeSpan.FromSeconds(.5));
}
LoadingText.Text = "Done!";
}
private async Task SimulateLongRunningTask()
{
await Task.Delay(TimeSpan.FromSeconds(10));
}
}
}
Here is a recording of it running, with window interaction proving that the UI is not blocked.
As an extra bonus, I got bored and implemented the extension method I mentioned (with super special bonus, a "local function" feature from C# 7!).
public static class TaskExtensions
{
public static async Task WithSpinner(this Task longRunningTask, TextBlock spinnerTextBox)
{
async Task ShowSpinner()
{
var numDots = 0;
while (!longRunningTask.IsCompleted)
{
if (numDots++ > 3) numDots = 0;
spinnerTextBox.Text = $"Waiting{new string('.', numDots)}";
await Task.Delay(TimeSpan.FromSeconds(.5));
}
spinnerTextBox.Text = "Done!";
}
var spinner = ShowSpinner();
var tasks = new List<Task>
{
longRunningTask,
spinner,
};
await Task.WhenAll(tasks);
}
}
You use it like this:
await SimulateLongRunningTask().WithSpinner(LoadingTextBlock);
If you use the await, it means that your code will wait for the async operation to finish at that line, and then continue.
That is why your progress task is not started until task task is finished.
You can create a background thread that runs in parallel with the task until it is finished and in there you can tell the UI thread to animate the dots once per second. Since the UI thread is NOT blocked (but only waiting for the task to finish), this works.
Example code:
string originalLblContent = (lblProgress.Content as string) ?? "";
bool taskStarted = false;
var progressThread = new Thread((ThreadStart)delegate
{
// this code will run in the background thread
string dots = "";
while(!taskStarted || !task.IsCompleted) {
if(dots.Length < 3) {
dots += ".";
} else {
dots = "";
}
// because we are in the background thread, we need to invoke the UI thread
// we can invoke it because your task is running asynchronously and NOT blocking the UI thread
Dispatcher.Invoke(() =>
{
lblProgress.Content = originalLblContent + dots;
});
Thread.Sleep(1000);
}
});
progressThread.Start();
taskStarted = true;
await task;
// the task is now finished, and the progressThread will also be after 1 second ...
Your approach is a little funky here. The await statements will prevent the method returning until each thread is finished. The await feature is not a completely asynchronous execution (why would it be? you have threads for that).
You need to re-think your approach to the problem. Fundamentally, you want to update the UI while another process is in progress. This calls for multithreading.
From Microsoft:
"Handling blocking operations in a graphical application can be difficult. We don’t want to call blocking methods from event handlers because the application will appear to freeze up. We can use a separate thread to handle these operations, but when we’re done, we have to synchronize with the UI thread because we can’t directly modify the GUI from our worker thread. We can use Invoke or BeginInvoke to insert delegates into the Dispatcher of the UI thread. Eventually, these delegates will be executed with permission to modify UI elements.
In this example, we mimic a remote procedure call that retrieves a weather forecast. We use a separate worker thread to execute this call, and we schedule an update method in the Dispatcher of the UI thread when we’re finished."
https://learn.microsoft.com/en-us/dotnet/framework/wpf/advanced/threading-model
I am having difficulty updating the UI using Xamarin. The objective is to make reactive UI so the user knows that application is thinking. Below are my attempts.
Attempt 1
private void BeginProcess(string fileName)
{
Device.BeginInvokeOnMainThread(() => {
iContent.Text = "UI Successfully updated.";
});
Device.BeginInvokeOnMainThread(() => {
ProccessFolder testMethod = new ProccessFolder.Initialize(fileName);
});
}
Attempt 2
private void UpdateUI () {
iContent.Text = "UI Successfully updated.";
}
private void BeginProcess(string fileName)
{
System.Threading.Thread t = new System.Threading.Thread(UpdateUI);
t.Priority = System.Threading.ThreadPriority.Highest;
t.IsBackground = false;
t.Start();
Device.BeginInvokeOnMainThread(() => {
ProccessFolder testMethod = new ProccessFolder.Initialize(fileName);
});
}
Attempt 3
private void BeginProcess(string fileName)
{
Device.BeginInvokeOnMainThread(() => {
iContent.Text = "UI Successfully updated.";
});
Task.Delay(5000);
Device.BeginInvokeOnMainThread(() => {
ProccessFolder testMethod = new ProccessFolder.Initialize(fileName);
});
}
Unfortunately none of these work. What does work is if I place the ProcessFolder method in a background thread and invoke the UI changes on the main thread. However the completion time of the ProcessFolder method is slower.
Any suggestion on how I can update the UI while still executing ProcessFolder on the main thread?
Sometimes when your try update something on the main ui from within a method, depending on the way you've written (and/or structured) it can mean that the main dispatcher waits for the method to complete before updating the main ui.
If you were to try the following example it would successfully complete a UI update after each foreach iteration because when it completes it's initial task it wants to return out of the for loop and hits our main thread invoke, which HAS to complete before the loop can iterate again.
private void BeginProcess()
{
Task.Run(()=>{
for (int i = 0; i < 100; i++)
{
// Perform a task
BeginInvokeOnMainThread(() => {
iContent.Text = "UI Successfully updated: " + i " times out of 100";
});
}
})
}
You can sometimes replicate this kind of effect by using:
NSRunLoop.Current.RunUntil(1000);
to allow the UI to catch up when you call the 'BeginInvokeOnMainThread' delegate.
First of all, all the UI updates must be done in the main thread.
For your particular problem maybe you could use async/await (https://msdn.microsoft.com/library/hh191443(vs.110).aspx)
You could do something like in the main thread:
ProccessFolder testMethod = await ProccessFolder.Initialize(fileName);
iContent.Text = "UI Successfully updated.";
You have to make the Initialize method async and to return a task
there have been some time since i worked with tasks and lambda expressions. Is this a good way to run a anonymous task with a lambda expression and then run code on the UI thread when task is finished?
private void btn_mods_Click(object sender, RoutedEventArgs e)
{
function_buttons_stackpanel.IsEnabled = false;
Loading();
Task task = new Task(() => {
if (IsServiceIsUp() != false)
{
webServiceMods = JsonConvert.DeserializeObject(_webServiceResponse).mods;
webServiceBaseUrl = JsonConvert.DeserializeObject(_webServiceResponse).basePath;
Console.Write(webServiceBaseUrl);
}
});
task.Start();
task.ContinueWith((foo) =>
{
FinishedLoading();
function_buttons_stackpanel.IsEnabled = true;
}, TaskScheduler.FromCurrentSynchronizationContext());
}
private void Loading()
{
img_loading.Visibility = Visibility.Visible;
}
private void FinishedLoading()
{
img_loading.Visibility = Visibility.Collapsed;
}
I tried to chain the task.Start directly but that gave me an error Cannot Implicitly convert type void to System.Threading.Tasks.Task.
Basically what i wanted to do was to chain the whole process from start to end.
Task task = new Task(() => {
if (IsServiceIsUp() != false)
{
webServiceMods = JsonConvert.DeserializeObject(_webServiceResponse).mods;
webServiceBaseUrl = JsonConvert.DeserializeObject(_webServiceResponse).basePath;
Console.Write(webServiceBaseUrl);
}
}).Start();
In PHP I would do something like this:
$task = new Task(() => {
if (IsServiceIsUp() != false)
{
$webServiceMods = JsonConvert::DeserializeObject($_webServiceResponse).mods;
$webServiceBaseUrl = JsonConvert::DeserializeObject($_webServiceResponse).basePath;
Console::Write($webServiceBaseUrl);
}
})
->Start()
->ContinueWith(($foo) =>
{
FinishedLoading();
$function_buttons_stackpanel.IsEnabled = true;
}, TaskScheduler::FromCurrentSynchronizationContext());
Is this possible? If so, is there any reason to not do it, and if there is a better way to do this, could you give me an example?
And thanks!
You can do this rather easily and a bit cleaner with async-await:
private async void btn_mods_Click(object sender, RoutedEventArgs e)
{
if (!IsServiceIsUp())
return;
function_buttons_stackpanel.IsEnabled = false;
Loading();
await Task.Run(() =>
{
var result = JsonConvert.DeserializeObject(_webServiceResponse);
Console.Write(result.webServiceBaseUrl);
});
FinishedLoading();
function_buttons_stackpanel.IsEnabled = true;
}
Performance wise, I wouldn't be so sure you'd need to use a threadpool thread just for deserializing a JSON. I would definitely test this code to determine if it's worth it.
If you declare your event handler as async function, then you don't have to start the task, it already runs asynchronously.
All async functions should return Task instead of void and Task<TResult> instead of TResult. The only exception is the event handler. The event handler returns void
In proper async-await this would be as follows:
private async void btn_mods_Click(object sender, RoutedEventArgs e)
{
function_buttons_stackpanel.IsEnabled = false;
Loading();
if (IsServiceIsUp())
{ // The service is up, start the deserialization
// because this function is async, you can call other async functions
// without having to start a task
// The UI remains responsive
webServiceMods = await JsonConvert.DeserializeObjectAsync(_webServiceResponse).mods;
// because of the await above, the second task is started after the first is finished:
webServiceBaseUrl = await JsonConvert.DeserializeObjectAsync(_webServiceResponse).basePath;
// because of the await the second task is also finished
Console.Write(webServiceBaseUrl);
FinishedLoading();
function_buttons_stackpanel.IsEnabled = true;
}
}
By making your event handler async your code will make full usage of async-await. Your code would look much neater without the magic of continueWith and other Task related functions from before the async-await era.
One final remark: the following code looks silly and unnecessary difficult:
if (IsServiceIsUp() != false) ...
This should of course be:
if (IsServiceIsUp()) ...
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 8 years ago.
Improve this question
The code below is causing my WPF application to get hung (likely dead-locks). I have verified that DownloadStringAsTask method is executed on a separate (non-UI) thread. Interestingly if you uncomment the messagebox line (just before call to while (tasks.Any()), application works fine. Can anyone explain why do application hungs at first place and also how to resolve this issue?
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="9*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Frame x:Name="frame" Grid.Row="0" />
<StatusBar VerticalAlignment="Bottom" Grid.Row="1" >
<StatusBarItem>
<TextBlock Name="tbStatusBar" Text="Waiting for getting update" />
</StatusBarItem>
</StatusBar>
</Grid>
</Window>
public partial class MainWindow : Window
{
List<string> URLsToProcess = new List<string>
{
"http://www.microsoft.com",
"http://www.stackoverflow.com",
"http://www.google.com",
"http://www.apple.com",
"http://www.ebay.com",
"http://www.oracle.com",
"http://www.gmail.com",
"http://www.amazon.com",
"http://www.outlook.com",
"http://www.yahoo.com",
"http://www.amazon124.com",
"http://www.msn.com"
};
public MainWindow()
{
InitializeComponent();
ProcessURLs();
}
public void ProcessURLs()
{
var tasks = URLsToProcess.AsParallel().Select(uri => DownloadStringAsTask(new Uri(uri))).ToArray();
//MessageBox.Show("this is doing some magic");
while (tasks.Any())
{
try
{
int index = Task.WaitAny(tasks);
this.tbStatusBar.Text = string.Format("{0} has completed", tasks[index].AsyncState.ToString());
tasks = tasks.Where(t => t != tasks[index]).ToArray();
}
catch (Exception e)
{
foreach (var t in tasks.Where(t => t.Status == TaskStatus.Faulted))
this.tbStatusBar.Text = string.Format("{0} has completed", t.AsyncState.ToString());
tasks = tasks.Where(t => t.Status != TaskStatus.Faulted).ToArray();
}
}
}
private Task<string> DownloadStringAsTask(Uri address)
{
TaskCompletionSource<string> tcs = new TaskCompletionSource<string>(address);
WebClient client = new WebClient();
client.DownloadStringCompleted += (sender, args) =>
{
if (args.Error != null)
tcs.SetException(args.Error);
else if (args.Cancelled)
tcs.SetCanceled();
else
tcs.SetResult(args.Result);
};
client.DownloadStringAsync(address);
return tcs.Task;
}
}
The biggest problem here is that your constructor does not return until all of the tasks have completed. Until the constructor returns, the window will not be shown, because the window messages related to drawing the window aren't going to be processed.
Note that you don't really have "deadlock" here per se. Instead, if you waited long enough (i.e. until all the tasks have completed), the window would actually be shown.
When you add the call to MessageBox.Show(), you give the UI thread a chance to process the window message queue. That is, the normal modal dialog includes a thread message pump which winds up handling those messages in the queue, including those related to showing your window. Note that even if you add the MessageBox.Show(), that won't result in the window being updated as your processing progresses. It just allows the window to be shown before you block the UI thread again.
One way to address this is to switch to the async/await pattern. For example:
public MainWindow()
{
InitializeComponent();
var _ = ProcessURLs();
}
public async Task ProcessURLs()
{
List<Task<string>> tasks = URLsToProcess.Select(uri => DownloadStringAsTask(new Uri(uri))).ToList();
while (tasks.Count > 0)
{
Task<string> task = await Task.WhenAny(tasks);
string messageText;
if (task.Status == TaskStatus.RanToCompletion)
{
messageText = string.Format("{0} has completed", task.AsyncState);
// TODO: do something with task.Result, i.e. the actual downloaded text
}
else
{
messageText = string.Format("{0} has completed with failure: {1}", task.AsyncState, task.Status);
}
this.tbStatusBar.Text = messageText;
tasks.Remove(task);
}
tbStatusBar.Text = "All tasks completed";
}
I've rewritten the ProcessURLs() method as an async method. This means that when the constructor calls it, it will run synchronously up to the first await statement, at which point it will yield and allow the current thread to continue normally.
When the call to Task.WhenAny() completes (i.e. any of the tasks complete), the runtime will resume execution of the ProcessURLs() method by invoking the continuation on the UI thread. This allows the method to access the UI objects (e.g. this.tbStatusBar.Text) normally, while occupying the UI thread only long enough to process the completion.
When the loop returns to the top and the Task.WhenAny() method is called again, the whole sequence is repeated (i.e. just the way a loop is supposed to work :) ).
Some other notes:
The var _ = bit in the constructor is there to suppress the compiler warning that would otherwise occur when the Task return value is ignored.
IMHO, it would be better to not initialize these operations in the constructor. The constructor is just generally a bad place to be doing significant work like this. Instead, I would (for example) override the OnActivated() method, making it async so you can use the await statement with the call to ProcessURLs() (i.e. a more idiomatic way to call an async method). This ensures the window is completely initialized and shown before you start doing any other processing.
In this particular example, starting the processing in the constructor is probably not really going to hurt anything, as long as you're using async/await, since the UI-related stuff isn't going to be able to be executed in any case until at least the constructor has returned. I just try to avoid doing this sort of thing in the constructor as a general rule.
I also modified the general handling of your task collection, to something that I feel is somewhat more suitable. It gets rid of the repeated reinitialization of the tasks collection, as well as takes advantage of the semantics of the WhenAny() method. I also removed the AsParallel(); given that the long-running part of the processing is handled asynchronously already, there did not seem to be any advantage in the attempt to parallelize the Select() itself.
The likely cause for the hang is that you are mixing sync and asnyc code and calling WaitAny. Stephen Cleary has post that is useful in understanding common issues with Tasks.
Best Practices in Asynchronous Programming
Here is a solution that simplifies your code and uses Parallel.ForEach
Code
public partial class WaitAnyWindow : Window {
private List<string> URLsToProcess = new List<string>
{
"http://www.microsoft.com",
"http://www.stackoverflow.com",
"http://www.google.com",
"http://www.apple.com",
"http://www.ebay.com",
"http://www.oracle.com",
"http://www.gmail.com",
"http://www.amazon.com",
"http://www.outlook.com",
"http://www.yahoo.com",
"http://www.amazon.com",
"http://www.msn.com"
};
public WaitAnyWindow02() {
InitializeComponent();
Parallel.ForEach(URLsToProcess, (x) => DownloadStringFromWebsite(x));
}
private bool DownloadStringFromWebsite(string website) {
WebClient client = new WebClient();
client.DownloadStringCompleted += (s, e) =>
{
if (e.Error != null)
{
Dispatcher.BeginInvoke((Action)(() =>
{
this.tbStatusBar.Text = string.Format("{0} didn't complete because {1}", website, e.Error.Message);
}));
}
else
{
Dispatcher.BeginInvoke((Action)(() =>
{
this.tbStatusBar.Text = string.Format("{0} has completed", website);
}));
}
};
client.DownloadStringAsync(new Uri(website));
return true;
}
}
I'm using incremental loading to show a ListView items. I run LoadDetails method in the background thread using Task.Run(...) to not busy the UI thread.
But it still blocks the UI thread and it doesn't render UI elements until it finishes the task.
executing LoadDetails method takes around 3 seconds to complete.
private async void LoadItemCounts(ListViewBase sender, ContainerContentChangingEventArgs args)
{
if (args.Phase != 6)
{
throw new Exception("Not in phase 6");
}
var item = args.Item as ItemModel;
var templateRoot = (Grid)args.ItemContainer.ContentTemplateRoot;
var textBlock = (TextBlock)templateRoot.FindName("textBlock");
await Task.Run(() => LoadDetails(textBlock, item.Id));
}
private async Task LoadDetails(TextBlock textBlock, string id)
{
int count = await DataSource.GetItemCounts(id);
await this.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
textBlock.Text = count.ToString();
});
}
How to fix this so it doesn't block the UI thread? thanks.
(It's a Windows Phone Runtime app)
It's not clear from your question how you are measuring the 3 second delay. Is it that the call to GetItemCounts() itself takes 3 seconds? If so, isn't that to be expected? The delay is why you would execute that asynchronously in the first place, isn't it?
The code you posted doesn't really seem quite right. Since your new Task doesn't await the call to LoadDetails(), that task will finish right away, without any synchronization with the actual work being done. Written differently, you could also avoid having to call through the Dispatcher directly.
I would have written it something more like this:
private async void LoadItemCounts(ListViewBase sender, ContainerContentChangingEventArgs args)
{
if (args.Phase != 6)
{
throw new Exception("Not in phase 6");
}
var item = args.Item as ItemModel;
var templateRoot = (Grid)args.ItemContainer.ContentTemplateRoot;
var textBlock = (TextBlock)templateRoot.FindName("textBlock");
await LoadDetails(textBlock, item.Id);
}
private async Task LoadDetails(TextBlock textBlock, string id)
{
int count = await DataSource.GetItemCounts(id);
textBlock.Text = count.ToString();
}
I.e. as long as you keep awaiting on the UI thread, you don't need to invoke via the Dispatcher. Note that the above assumes you need the LoadDetails() method, presumably because you call it from multiple places and some require this particular implementation for some reason. But note that you could have just written the LoadItemCounts() method like this, and left out the LoadDetails() method altogether:
private async void LoadItemCounts(ListViewBase sender, ContainerContentChangingEventArgs args)
{
if (args.Phase != 6)
{
throw new Exception("Not in phase 6");
}
var item = args.Item as ItemModel;
var templateRoot = (Grid)args.ItemContainer.ContentTemplateRoot;
var textBlock = (TextBlock)templateRoot.FindName("textBlock");
textBlock.Text = (await DataSource.GetItemCounts(id)).ToString();
}
It looks like your code is correctly not blocking the UI thread by using await, but since LoadItemDetails() is presumably being called on the UI thread, it won't finish until the method is finished doing its work.
To fix this, just omit the await on the call to Task.Run(), so something like
Task.Run(() => LoadDetails(textBlock, item.Id));
should make LoadItemDetails() return immediately.