Reputation: 1155
I have a long running task and I don't want to block the UI, so I got a DispatcherTimer
and I use it's tick event to check the task property IsCompleted
but this causes some sort of deadlock because my application stops responding
public partial class MainWindow : Window
{
DateTime beginFirstPhase;
DateTime beginSecondPhase;
DispatcherTimer dispatcherTimer = new DispatcherTimer();
IEnumerable<string> collection;
Task firstPhaseTask;
Task secondPhaseTask;
public MainWindow()
{
InitializeComponent();
}
private void button_Click(object sender, RoutedEventArgs e)
{
progressTxtBox.AppendText("Entering button click event handler\n");
beginFirstPhase = DateTime.Now;
dispatcherTimer.Tick += DispatcherTimer_Tick_FirstPhase;
dispatcherTimer.Interval = new TimeSpan(0, 0, 5);
dispatcherTimer.Start();
progressTxtBox.AppendText("Begining First Phase now\n");
firstPhaseTask = Task.Factory.StartNew(() =>
/*this is basically a big linq query over a huge collection of strings
(58 thousand+ strings). the result of such query is stored in the field named
collection, above*/), TaskCreationOptions.PreferFairness);
progressTxtBox.AppendText("Awaiting First Phase completion...\n");
}
private void DispatcherTimer_Tick_FirstPhase(object sender, EventArgs e)
{
TimeSpan span = DateTime.Now - beginFirstPhase;
//not even the line bellow is executed.
statusTextBlock.Text = $"Running: {span.ToString()}";
if (firstPhaseTask.IsCompleted)
{
dispatcherTimer.Stop();
progressTxtBox.AppendText($"First Phase completed in {span.ToString()}\n");
secondPhase();
}
}
private void secondPhase()
{
beginSecondPhase = DateTime.Now;
progressTxtBox.AppendText("Begining Second Phase now\n"));
dispatcherTimer.Tick -= DispatcherTimer_Tick_FirstPhase;
dispatcherTimer.Tick += DispatcherTimer_Tick_SecondPhase;
dispatcherTimer.Interval = new TimeSpan(0, 0, 5);
dispatcherTimer.Start();
int counter = 0;
secondPhaseTask = Task.Factory.StartNew(() =>
{
foreach (string str in collection)
{
Dispatcher.Invoke(() => progressTxtBox.AppendText($"iteration <{counter++}>\n"));
IEnumerable<Tuple<string, string> queryResult; // = another linq query
foreach (var tuple in queryResult)
{
Dispatcher.Invoke(() => outputListView.Items.Add($"{tuple.Item1} {tuple.Item2} {str}"));
}
}
}, TaskCreationOptions.PreferFairness);
}
private void DispatcherTimer_Tick_SecondPhase(object sender, EventArgs e)
{
TimeSpan span = DateTime.Now - beginSecondPhase;
statusTextBlock.Text = $"Running: {span.ToString()}";
if (secondPhaseTask.IsCompleted)
{
dispatcherTimer.Stop();
progressTxtBox.AppendText($"Second Phase completed in {span.ToString()}\n");
progressTxtBox.AppendText("Execution Complete");
}
}
}
What causes this to block? Does Task.IsCompleted
blocks the caller's thread??
Isn't it possible to poll a Task like this at all? if not, is there another option?
Upvotes: 3
Views: 1443
Reputation: 11677
Is there a reason you're not using async
and await
?
await Task.Factory.StartNew(() =>
/*this is basically a big linq query over a huge collection of strings
(58 thousand+ strings). the result of such query is stored in the field named
collection, above*/), TaskCreationOptions.PreferFairness);
// StartSecondPhase won't get called until the Task returned by Task.Factory.StartNew is complete
await StartSecondPhase();
Upvotes: 0
Reputation: 86
Personally, I would approach this way: Find a show busy animation service that you like, google is your friend.
Invoke the busy animation (Tell the user to wait, you may find one that let you update the current state as well)
run the operation like already suggested but with minimum modification like this:
this.BusyService.ShowBusy();
Task t = Task.Factory.StartNew(() =>
PhaseOne();
PhaseTwo();
), TaskCreationOptions.PreferFairness);
t.ContinueWith(()=>{ this.BusyService.HideBusy(); });
I'm talking about a service for the showbusy because I associate it with Prism and WPF but also a simple WinForm can do the trick and there's sample example even here on SO I think.
What will happen: UI is blocked but not freezed, user will know he has to wait, on the task callback you will release the UI.
Hope this help.
EDIT: Let's approach this a bit differently, here's a demo for WinForms of the Busy indicator I was talking about: BusySample
There's a BusyForm which is just a modeless form with a multiline textbox where you're gonna write your update text. Plus there's the main form who use it like this:
private BackgroundWorker worker;
private BusyForm busyForm = new BusyForm();
private string progressText;
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
progressText = "Entering button click event handler" + Environment.NewLine;
busyForm.SetText(progressText);
worker = new BackgroundWorker();
worker.DoWork += worker_DoWork;
worker.RunWorkerCompleted += worker_RunWorkerCompleted;
worker.RunWorkerAsync();
busyForm.ShowDialog();
}
private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
busyForm.Hide();
}
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
progressText += "Begining First Phase now" + Environment.NewLine;
this.Invoke((MethodInvoker)delegate
{
busyForm.SetText(progressText);
});
PhaseOne();
progressText += "First Phase complete..." + Environment.NewLine + "Begining Second Phase now" + Environment.NewLine;
this.Invoke((MethodInvoker)delegate
{
busyForm.SetText(progressText);
});
PhaseTwo();
progressText += "Execution Complete";
this.Invoke((MethodInvoker)delegate
{
busyForm.SetText(progressText);
});
System.Threading.Thread.Sleep(2000); //Just adding a delay to let you see this is shown
}
private void PhaseOne()
{
System.Threading.Thread.Sleep(2000);
}
private void PhaseTwo()
{
System.Threading.Thread.Sleep(2000);
}
BackgroundWorker is taking care of all stuff and it's running on a separate thread, anyway you need to update the text using:
this.Invoke((MethodInvoker)delegate
{
busyForm.SetText(progressText);
});
Because you need to update UI from the UI thread. I wrote the sample in like 10 minutes and I didn't tested it much but that should give you an idea.
Upvotes: 0
Reputation: 6963
You want to "close" over the Task.Run, by using await operator. That way you get to tell user "Awaiting..." then when task completes you are on Gui thread automatically. If you want progress reports I think this is done via the Progress class but can't remember. Anyway this should get you close...
private async void button_Click(object sender, RoutedEventArgs e)
{
progressTxtBox.AppendText("Entering button click event handler\n");
beginFirstPhase = DateTime.Now;
dispatcherTimer.Tick += DispatcherTimer_Tick_FirstPhase;
dispatcherTimer.Interval = new TimeSpan(0, 0, 5);
dispatcherTimer.Start();
progressTxtBox.AppendText("Begining First Phase now\n");
progressTxtBox.AppendText("Awaiting First Phase completion...\n");
firstPhaseTask =await Task.Factory.StartNew(() =>
/*this is basically a big linq query over a huge collection of strings
(58 thousand+ strings). the result of such query is stored in the field named
collection, above*/), TaskCreationOptions.PreferFairness);
progressTxtBox.AppendText("First Phase complete...\n");
}
I'd also suggest changing the results to be...
var results =await Task<IEnumerable<OfType>>.Factory.StartNew(() =>{
return context.where(p=>p.field = fieldvalue);
Upvotes: 1