2013-07-31 54 views
17

我已經在BackgroundWorker中使用過這種類型的東西,但我想使用.NET 4.5的新異步/等待方法。我可能會吠叫錯誤的樹。請指教。使用WinForms進行異步/等待ProgressBar

目標:創建一個組件,它將執行一些長時間運行的工作,並在進行工作時顯示帶有進度條的模態窗體。該組件將獲取窗口的句柄,以在執行長時間運行的工作時阻止交互。

狀態:請參閱下面的代碼。我以爲自己做得很好,直到我嘗試與窗口交互。如果我把事情放在一邊(即不要碰!),一切都會「完美地」運行,但是如果我點擊任一窗口,程序就會在長時間運行結束後掛起。實際的交互(拖動)被忽略,就像UI線程被阻塞一樣。

問題:我的代碼可以很容易地修復嗎?如果是這樣,怎麼樣?或者,我應該使用其他方法(例如BackgroundWorker)嗎?

代碼(Form1上是一個標準的形式與進度和公共方法,的UpdateProgress中,設定進度的值):

using System; 
using System.Diagnostics; 
using System.Threading; 
using System.Threading.Tasks; 
using System.Windows.Forms; 

namespace ConsoleApplication1 
{ 
class Program 
{ 
    static void Main(string[] args) 
    { 
     Console.WriteLine("Starting.."); 
     var mgr = new Manager(); 
     mgr.GoAsync(); 
     Console.WriteLine("..Ended"); 
     Console.ReadKey(); 
    } 
} 

class Manager 
{ 
    private static Form1 _progressForm; 

    public async void GoAsync() 
    { 
     var owner = new Win32Window(Process.GetCurrentProcess().MainWindowHandle); 
     _progressForm = new Form1(); 
     _progressForm.Show(owner); 

     await Go(); 

     _progressForm.Hide(); 
    } 

    private async Task<bool> Go() 
    { 
     var job = new LongJob(); 
     job.OnProgress += job_OnProgress; 
     job.Spin(); 
     return true; 
    } 

    void job_OnProgress(int percent) 
    { 
     _progressForm.UpdateProgress(percent); 
    } 
} 

class LongJob 
{ 
    public event Progressed OnProgress; 
    public delegate void Progressed(int percent); 

    public void Spin() 
    { 
     for (var i = 1; i <= 100; i++) 
     { 
      Thread.Sleep(25); 
      if (OnProgress != null) 
      { 
       OnProgress(i); 
      } 
     } 
    } 
} 

class Win32Window : IWin32Window 
{ 
    private readonly IntPtr _hwnd; 
    public Win32Window(IntPtr handle) 
    { 
     _hwnd = handle; 
    } 
    public IntPtr Handle 
    { 
     get 
     { 
      return _hwnd; 
     } 
    } 
} 
} 

回答

7

@ StephenCleary的回答是正確的。雖然,我不得不對他的回答做一些修改,以獲得我認爲OP所需要的行爲。

public void GoAsync() //no longer async as it blocks on Appication.Run 
{ 
    var owner = new Win32Window(Process.GetCurrentProcess().MainWindowHandle); 
    _progressForm = new Form1(); 

    var progress = new Progress<int>(value => _progressForm.UpdateProgress(value)); 

    _progressForm.Activated += async (sender, args) => 
     { 
      await Go(progress); 
      _progressForm.Close(); 
     }; 

    Application.Run(_progressForm); 
} 
+0

如果Go(progress)拋出一個異常,那麼_progressForm.Close()永遠不會被調用 - >模式對話框將永遠掛起 – Hiep

+0

並且最好將「_progressForm.Activated」更改爲「_progressForm.Shown」,因爲_progressForm可能被激活多次在他一生中的時間.. - >去(進展)將被稱爲多次.. – Hiep

+0

https://gist.github.com/duongphuhiep/f83f98593d93045e717f – Hiep

18

asyncawait關鍵字並不意味着「在後臺運行線。」我有一個async/await intro on my blog,它描述了他們做什麼的意思。您必須明確地將CPU綁定操作放在後臺線程上,例如Task.Run

此外,Task-based Asynchronous Pattern文檔描述了代碼的常用方法,例如進度報告。

class Manager 
{ 
    private static Form1 _progressForm; 

    public async Task GoAsync() 
    { 
    var owner = new Win32Window(Process.GetCurrentProcess().MainWindowHandle); 
    _progressForm = new Form1(); 
    _progressForm.Show(owner); 

    var progress = new Progress<int>(value => _progressForm.UpdateProgress(value)); 
    await Go(progress); 

    _progressForm.Hide(); 
    } 

    private Task<bool> Go(IProgress<int> progress) 
    { 
    return Task.Run(() => 
    { 
     var job = new LongJob(); 
     job.Spin(progress); 
     return true; 
    }); 
    } 
} 

class LongJob 
{ 
    public void Spin(IProgress<int> progress) 
    { 
    for (var i = 1; i <= 100; i++) 
    { 
     Thread.Sleep(25); 
     if (progress != null) 
     { 
     progress.Report(i); 
     } 
    } 
    } 
} 

注意,Progress<T>類型正確處理線程編組,因此沒有必要範圍內Form1.UpdateProgress編組。

+0

您的更改不會產生所需的結果。由於OP在'Console'應用程序中運行,因此不存在'SynchronizationContext'。我認爲需要引入'進度'才能正常工作? – YK1

+0

Windows窗體組件在創建時會建立一個'WinFormsSynchronizationContext','Progress '不需要'SynchronizationContext'(儘管它可以更好地工作)。 –

+0

我把一個斷點放到傳遞給'Progress '的委託中 - 它永遠不會被觸及 - 雖然工作正在旋轉,但UI只是掛起。我認爲'Application.Run()'需要建立'SynchronizationContext' - '_progressForm.Show'建立一個嗎?我不確定。 – YK1

3
private async void button1_Click(object sender, EventArgs e) 
{ 
    IProgress<int> progress = new Progress<int>(value => { progressBar1.Value = value; }); 
    await Task.Run(() => 
    { 
     for (int i = 0; i <= 100; i++) 
      progress.Report(i); 
    }); 
} 

糾正我,如果我錯了,但是這似乎是更新進度條的最簡單方法。