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;
}
}
Related
I have been deploying updates for an application of mine with ClickOnce for a while. While I'm happy to be able to make improvements, I'm a little frustrated with the current progress bar. A little background - I have a XAML window class called "UpdateProgress" that I open when an update is being undertaken for the application. Here's the current code snippet I'm using right now, which does at least notify the user that progress is being made without freezing the application/crashing, but DOES NOT visually update the progress bar:
case UpdateStatuses.UpdateAvailable:
DialogResult dialogResult = System.Windows.Forms.MessageBox.Show("An update is available. Would you like to update the application now?", "Update available", MessageBoxButtons.OKCancel);
if (dialogResult.ToString() == "OK")
{
BackgroundWorker bgUpdate = new BackgroundWorker();
UpdateProgress updateNotify = new UpdateProgress();
bgUpdate.WorkerReportsProgress = true;
bgUpdate.DoWork += (uptSender, uptE) => { UpdateApplication();};
bgUpdate.ProgressChanged += (progSender, progE) => { updateNotify.updateProgress.Value = progE.ProgressPercentage; };
bgUpdate.RunWorkerCompleted += (comSender, comE) => {
updateNotify.Close();
applicationUpdated();
};
updateNotify.Show();
bgUpdate.RunWorkerAsync();
}
break;
Basically, I'm creating a background worker above, which runs the code below:
private static void UpdateApplication()
{
try
{
ApplicationDeployment updateCheck = ApplicationDeployment.CurrentDeployment;
//BackgroundWorker bgWorker = new BackgroundWorker();
//UpdateProgress updateNotify = new UpdateProgress();
//updateCheck.UpdateProgressChanged += (s, e) =>
//{
// updateNotify.updateProgress.Value = e.ProgressPercentage;
//};
//bgWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(UpdateComponent.noteUpdates);
//bgWorker.RunWorkerAsync();
//updateCheck.UpdateCompleted += (s, e) =>
//{
// updateNotify.Close();
// applicationUpdated();
//};
//updateNotify.Dispatcher.InvokeAsync(() =>
// {
//updateNotify.Show();
updateCheck.Update();
//});
//return null;
}
catch (DeploymentDownloadException dde)
{
System.Windows.MessageBox.Show("Cannot install the latest version of the application. Please check your network connection, or try again later. Error: " + dde);
//return null;
}
}
Quick explanation, currently I'm only creating an "ApplicationDeployment" instance called "updateCheck" and just having it run the update in this thread. What I've tried attempting before, is loading some of the commented code below, only to see the application crash when updating. Turns out, when debugging with a PROD instance of my application, it's due to the following error:
The calling thread cannot access this object because a different thread owns it.
Now, doing some digging, I've seen quite a few good reads about this. From what I understand, part of the problem is that I'm trying to run this code from a static class separated from my MainWindow and other UI classes. I'm doing this to try to keep my code clean and modular, but apparently, that comes with a price. I realize that one can bind the progress bar's progress percentage if it's in the code-behind of, say, the progress bar's window, but what if I'm trying to stick to running this in the static class I speak of instead? I've tried using things like the Dispatcher methods/BeginInvoke(), but unfortunately to end up with the same result.
Can someone give me the best suggestion on how to update the progress of my progress bar in a window with the percentage progress of an ApplicationDeployment instance's update routine?
Thanks a super ton in advance.
You're mis understanding the cause of your error. Any UI control should be updated from the thread that owns it.
First what you need to wrap is only the line of code that updates your progress bar.
Then you have two ways to wrap your call, either using IProgress<T> or Dispatcher. The former being quite cool as basically you're invoking an Action<T> and Progress<T> ensures to run it in the synchronization context it was instantiated, e.g. the UI thread. This is nice as basically you're abstracting things VS directly using the WPF Dispatcher.
Two really different approaches here, first is declared at caller then callee calls its Report method, second effectively wraps the call to UI in callee.
That's what you are executing during bgUpdate.ProgressChanged that needs to be taken care of.
And now if I were you I'd ditch BackgroundWorker in favor of Task since it's the preferred way to do that now, especially in WPF.
Smallest example using Task and IProgress:
Code:
<Window x:Class="WpfApp1.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"
mc:Ignorable="d">
<StackPanel>
<Button Content="DoWork" Click="Button1_Click" />
<ProgressBar Height="20" x:Name="ProgressBar1" Maximum="1.0"/>
</StackPanel>
</Window>
Code:
using System;
using System.Threading.Tasks;
using System.Windows;
namespace WpfApp1
{
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
}
private async void Button1_Click(object sender, RoutedEventArgs e)
{
var progress = new Progress<double>(s => { ProgressBar1.Value = s; });
await Task.Run(() => DoWork(progress));
}
private static async Task DoWork(IProgress<double> progress = null)
{
const int count = 100;
for (var i = 0; i < count; i++)
{
await Task.Delay(50);
progress?.Report(1.0d / (count - 1) * i);
}
}
}
}
Now you're code doesn't even need to know about Dispatcher that is WPF-specific, code could be anywhere, update any framework.
You could also cancel the operation with Task.Run:
https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.run?view=netframework-4.7.2
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 executing a potentially long running operation in the background thread of a modal dialog. The problem is that, when the operation takes a short time, the dialog is shown and closed almost instantaneously, which annoys the users.
I would like to show the dialog only if the operation takes longer than, say, 2s.
The dialog is a WPF Window and the long running operation code is in the ViewModel. The ViewModel creates a Task that runs the operation in the background.
Here is a relevant snippet:
public Task StartAction() {
var mainTask = Task.Factory.StartNew(InternalAction);
MainTask = mainTask;
mainTask.ContinueWith(_ => { IsFinished = true; });
return mainTask;
}
InternalAction is the potentially long running operation.
This is how I am trying to introduce the delay. I am using Sriram Sakthivel's suggestions from a different answer, but the code is not exactly the same:
var viewModel = ... // Creates the ViewModel
var dialogWindow = ... // Creates the Window and starts the operation by calling viewModel.StartAction();
var delayTask = Task.Delay(2000);
if (viewModel.MainTask != null) {
Task.WaitAny(delayTask, viewModel.MainTask);
}
if (viewModel.IsFinished) {
return;
}
ShowDialog(dialogWindow); // this code calls dialogWindow.ShowDialog() eventually
I am not using await because I do not want to yield control to the caller (COM) because the caller expects the result to be ready when it gets the control back.
I have been experimenting with different timeouts, e.g., 5000ms, and I do not see any difference in the behavior. The dialog windows still "blink" (are shown and closed immediately). I am sure I am doing something wrong, but I cannot understand my mistake.
You're waiting on MainTask, but MainTask isn't the task that sets IsFinished. You may be returning from WaitAny after InternalAction completes but before the IsFinished = true continuation completes.
Try setting MainTask to the continuation rather than its antecedent:
public Task StartAction() {
var mainTask = Task.Factory.StartNew(InternalAction);
var continuation = mainTask.ContinueWith(_ => { IsFinished = true; });
MainTask = continuation;
return mainTask;
}
Note that continuation cannot begin until mainTask has completed, so with this change you'll be waiting on mainTask and continuation.
Note, however, that if IsFinished is being read from the UI thread, you'll want to also set it from the UI thread. That, or make it backed by a volatile field.
There used to be a 3rd party Library called "Busy Indicator". Maybe you could enable it to only appear if the busy condition is met for a certain time? (https://github.com/xceedsoftware/wpftoolkit/wiki/Xceed-Toolkit-Plus-for-WPF).
Basically it comes down to the ViewModel exposing a "busy" property (or any property that can be converted into a boolean value representing "busy"). And the View reacting to the change on a delay (if any).
I am not sure if XAML itself can do that, as you need to show a window. A bit of code behind might be nesseary here. How about you register a custom ChangeNotification handler that starts a timer, with the timer re-checking if the condition is still met in the "tick" event?
Here is some code, made largely from memory:
//custom ChangeNofiticationHander
busyChangeHanlder(object sender, PropertyChangedEventArgs e){
if(e.PropertyName == "BusyBoolean"){
if(BusyBoolean)
//Start the timer
else
//Stop the timer
}
}
timerTickHandler(object sender, TimerTickEventArgs e){
if(BusyBoolean){
//create and Dispaly the Dialog here
}
}
var mainTask = Task.Delay(5000); // your long running task
if(Task.WaitAny(mainTask, Task.Delay(2000)) == 1){ // if the delay enden first, show dialog
showDialog();
await mainTask;
closeDialog();
}
await mainTask; // this will just skip, if mainTask is already done
Try this approach - it will only show dialog window, if the operation takes longer that 2s. You can also wrap all that in another task, then the caller can await the whole thing with no difference whether the dialog was shown or not.
I have a WPF application that will launch a long-running task (60+ seconds) that uses a System.Reactive.Subject<string> to push status messages periodically. The idea was that I could then Subscribe to the observable from my ViewModel and have ReactiveUI automatically update my UI through a Data Binding. This all works fine except that the TextBox is not updating in real-time. It only updates after long-running task has completed. I presume this is because my UI thread is being blocked and cannot update.
Working under that assumption, my research suggested that I could put the subscription on a background thread using SubscribeOn and then push the notifications back to the UI thread using ObserveOnDispatcher. However, this still did not produce the results that I wanted -- the UI only updated after long-running task had returned.
Can anybody give me some insight on what I need to do to allow my Output log to update in real time? Below are the related pieces of code.
XAML:
<TextBox Grid.Row="1" Text="{Binding Output}" IsReadOnly="True" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" Margin="10,0,10,10" x:Name="OutputTextBox" />
Code-Behind:
protected override void OnContentRendered(EventArgs e)
{
if (Converter == null) return;
_viewModel = new ConversionOutputWindowViewModel(Converter);
DataContext = _viewModel;
_viewModel.StartConversion(); // Long-running Task
//_viewModel.StartSave();
FinishButton.IsEnabled = true;
}
ViewModel:
private string _output;
public string Output // Data bound in XAML
{
get { return _output; }
set { this.RaiseAndSetIfChanged(ref _output, value); }
}
public void StartConversion()
{
_edmxConverter.Convert(); // Long-running Task
}
public ConversionOutputWindowViewModel(Utilities.Converters.EdmxConverter converter)
{
_edmxConverter = converter;
_compositeDisposable.Add(_edmxConverter.Output
.SubscribeOn(NewThreadScheduler.Default)
.ObserveOnDispatcher()
.Subscribe(s => Output = Output += s));
//_compositeDisposable.Add(_edmxConverter.Output.Subscribe(s => Output = Output += s));
}
Long-Running Task Function:
public Subject<string> Output { get; }
Output = new Subject<string>(); //In ctor
private void PrintReplacement(XAttribute attribute, string oldValue, string newValue, int level, Verbosity minVerbosity = Verbosity.Informational)
{
if (Verbosity < minVerbosity) return;
Output.OnNext($"{new string('\t', level)}{attribute.Name}: {oldValue} -> {newValue}{Environment.NewLine}");
}
Would it maybe help to wrap my Long-running Task function call inside an await Task.Run? I'm grasping at straws here. I don't have a very good working knowledge of .NET threading.
Well you are using subjects, so .SubscribeOn(NewThreadScheduler.Default) does nothing. (Standard old drum beating) Don't use subjects.
Your long running process should be a method call that returns an IObservable<T> (instead of having a class that exposes a property). The T should be the status updates you want to receive. When the task is done, then you OnComplete. If the task fails, you OnError.
Ideally the method that returns the IObservable using Observable.Create to deifne the work that will need to be done.
public IObservable<string> Convert()
{
return Observable.Create<string>(observer=>
{
//do stuff here.
//Call observer.OnNext with status messages
//When done call observer.OnCompleted()
});
}
As you dont show what your long running task is I cant help anymore with the implementation.
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.");
});
}
}