是否可以轮询任务完成?

问题描述:

我有一个长时间运行的任务,我不想阻止用户界面,所以我得到了一个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和异步,但是,由于我是自学的,因此我认为探索语言可以做什么是一个好主意。

这就像是我问了一个关于goto如何工作的问题,我得到的所有答案都是为什么不使用goto以及如何替换它。我明白你们都希望尽可能地提供帮助,为我提供我所选择的设计是不好的知识,但是如果不帮助我实施我的不良选择,那么你就拒绝了我有关如何去做的知识。我虚心相信这违背了这个社区的精神。我希望这个附录不会冒犯任何人,我只有尊重社区所有成员,我希望我已经说得很清楚。

你想通过使用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"); 

} 

我也建议改变的结果是...

​​
+0

这不会编译:'firstPhaseTask = await TaskFactor.StartNew(...)'。另外,我第二阶段只能在第一阶段完成后才开始,否则对象将处于不一致状态。这就是为什么我需要调查第一阶段是否完成。 – FinnTheHuman

+0

不,如果您使用闭包,则无需进行轮询,但它不会编译,因为您需要将代码更改为与我的建议类似。如果您需要查询结果,请按照建议中所示返回结果。当编译器命中await状态时,该线程会自动挂起,直到设置var结果的恢复。然后你是在主线程上,当第二次等待被击中时,如果你有一个“闭包”,同样的事情发生...... –

有没有你不使用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(); 
+0

所以这意味着我不能定期轮询一个“任务”完成?因为第一阶段未完成,第二阶段无法开始 – FinnTheHuman

+0

您可以进行投票,但为什么?没有理由,除非你想要目前的进展。 –

+0

@JohnPeters我也想要当前的进展。如果我可以投票,我该怎么做?检查Task.IsCompleted显然会导致死锁。 – FinnTheHuman

就个人而言,我会处理这个方式: 找到你喜欢的节目忙碌的动画服务,谷歌是你的朋友。

调用忙动画(告诉用户等待,你可能会找到一个让你更新当前状态为好)

运行操作就像已经建议,但以最小的修改是这样的:

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

我正在谈论showbusy的服务,因为我将它与Prism和WPF联系起来,但也有一个简单的WinForm可以实现这个技巧,甚至在这里也有示例示例。

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

希望得到这个帮助。

编辑: 让我们的方法,这一点不同,这里是为繁忙的指标的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分钟的时间写了样本,我没有做太多的测试,但这应该会给你一个想法。

+0

我不知道BusyService是什么。你能给我一个链接吗? – FinnTheHuman

+0

这个建议使用将会阻塞直到完成的延续。对?如下所示使用await关键字。 –

+0

@FinnTheHuman我用一个完整的工作样本更新了我的答案,即我对忙碌服务的含义。我在旅途中写了一个,但你可以改进它,或者只是在网上找到另一个。不幸的是,我无法给你一个链接或我在工作中使用的链接。 – Christopher