Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to poll a Task for completion?

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?

like image 487
FinnTheHuman Avatar asked Jan 21 '26 21:01

FinnTheHuman


1 Answers

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);
like image 186
JWP Avatar answered Jan 23 '26 11:01

JWP



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!