是否有可能轮询任务完成?

时间:2016-06-08 21:45:37

标签: c# wpf task deadlock

我有一个长时间运行的任务,我不想阻止用户界面,所以我得到了一个DispatcherTimer并使用它的tick事件检查任务属性IsCompleted但是这会导致某种排序死锁,因为我的应用程序停止响应

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");
        }
    }
}

导致此阻止的原因是什么? Task.IsCompleted阻止调用者的线程吗? 是不是可以像这样轮询任务?如果没有,还有其他选择吗?

编辑:亲爱的StackOverflow社区成员,我收到几个答案,基本上说“不要那样做,这种方式更好”。这些都是非常好的答案,我感谢你们所有人。但我的问题是:“是否可以轮询任务完成,以及如何完成”。没关系这是一个糟糕的设计模式,因为这不是问题的一部分。我知道我可以重写代码以使用await和async,但是,由于我是自学习的,我认为探索语言可以做什么是一个好主意。

就像我问了一个关于goto如何运作的问题,我得到的所有答案都是为什么不使用goto以及如何替换它。我理解你们都希望通过向我提供我所选择的设计不好的知识来尽可能地提供帮助,但是如果没有帮助我实施我的错误选择,那么你就是在拒绝我如何做到这一点的知识。我谦卑地认为这违背了这个社区的精神。我希望这个附录不会冒犯任何人,我除了尊重社区的所有成员外,我希望我能够做到这一点。

3 个答案:

答案 0 :(得分:1)

您希望通过使用await运算符“关闭”Task.Run。这样你就可以告诉用户“等待......”然后当任务完成时你会自动进入Gui线程。如果你想要进度报告,我认为这是通过Progress类完成的,但是不记得了。无论如何,这应该让你接近......

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");

}

我还建议将结果更改为......

 var results =await  Task<IEnumerable<OfType>>.Factory.StartNew(() =>{
          return context.where(p=>p.field = fieldvalue);

答案 1 :(得分:0)

您是否有理由不使用asyncawait

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();

答案 2 :(得分:0)

就个人而言,我会采用这种方式: 找一个你喜欢的节目繁忙的动画服务,谷歌是你的朋友。

调用繁忙的动画(告诉用户等待,你可以找到一个让你更新当前状态的动画)

按照已经建议的方式运行操作,但最小化修改如下:

this.BusyService.ShowBusy();
Task t = Task.Factory.StartNew(() =>
        PhaseOne();
        PhaseTwo();
), TaskCreationOptions.PreferFairness);
t.ContinueWith(()=>{ this.BusyService.HideBusy(); });

我正在谈论showbusy的一项服务,因为我将它与Prism和WPF联系起来,但是一个简单的WinForm也可以做到这一点,我想这就是样本示例。

会发生什么:UI被阻止但没有冻结,用户将知道他必须等待,在任务回调中你将释放UI。

希望得到这个帮助。

编辑: 让我们稍微改变一下,这是我正在讨论的Busy指标的WinForms演示: BusySample

有一个BusyForm,它只是一个带有多行文本框的无模式表单,您可以在其中编写更新文本。 另外还有使用它的主要形式:

         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正在处理所有内容并且它在一个单独的线程上运行,无论如何你需要使用以下命令更新文本:

this.Invoke((MethodInvoker)delegate
    {
        busyForm.SetText(progressText);
    });

因为您需要从UI线程更新UI。我在10分钟内编写了样本,但我没有对它进行过多次测试,但这应该会给你一个想法。