2016-06-08 90 views
1

我有一個長時間運行的任務,我不想阻止用戶界面,所以我得到了一個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以及如何替換它。我明白你們都希望儘可能地提供幫助,爲我提供我所選擇的設計是不好的知識,但是如果不幫助我實施我的不良選擇,那麼你就拒絕了我有關如何去做的知識。我虛心相信這違背了這個社區的精神。我希望這個附錄不會冒犯任何人,我只有尊重社區所有成員,我希望我已經說得很清楚。

回答

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

} 

我也建議改變的結果是...

​​
+0

這不會編譯:'firstPhaseTask = await TaskFactor.StartNew(...)'。另外,我第二階段只能在第一階段完成後纔開始,否則對象將處於不一致狀態。這就是爲什麼我需要調查第一階段是否完成。 – FinnTheHuman

+0

不,如果您使用閉包,則無需進行輪詢,但它不會編譯,因爲您需要將代碼更改爲與我的建議類似。如果您需要查詢結果,請按照建議中所示返回結果。當編譯器命中await狀態時,該線程會自動掛起,直到設置var結果的恢復。然後你是在主線程上,當第二次等待被擊中時,如果你有一個「閉包」,同樣的事情發生...... –

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

所以這意味着我不能定期輪詢一個「任務」完成?因爲第一階段未完成,第二階段無法開始 – FinnTheHuman

+0

您可以進行投票,但爲什麼?沒有理由,除非你想要目前的進展。 –

+0

@JohnPeters我也想要當前的進展。如果我可以投票,我該怎麼做?檢查Task.IsCompleted顯然會導致死鎖。 – FinnTheHuman

0

就個人而言,我會處理這個方式: 找到你喜歡的節目忙碌的動畫服務,谷歌是你的朋友。

調用忙動畫(告訴用戶等待,你可能會找到一個讓你更新當前狀態爲好)

運行操作就像已經建議,但以最小的修改是這樣的:

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

相關問題