2013-07-31 18 views
17

Ho avuto questo tipo di cose che funzionano in passato con un BackgroundWorker, ma voglio usare il nuovo approccio async/await di .NET 4.5. Potrei essere abbaiare sull'albero sbagliato. Si prega di avvisare.Async/Attesa con WinForms ProgressBar

Obiettivo: creare un componente che eseguirà un lavoro di lunga durata e mostrerà un modulo modale con una barra di avanzamento mentre esegue il lavoro. Il componente otterrà l'handle di una finestra per bloccare l'interazione mentre sta eseguendo il lavoro di lunga durata.

Stato: Vedere il codice di seguito. Pensavo di stare bene fino a quando ho provato a interagire con Windows. Se lascio le cose da solo (vale a dire non toccare!), Tutto gira "perfettamente", ma se faccio così tanto clic su una finestra il programma si blocca dopo il lavoro a lungo termine. Le interazioni reali (trascinamento) vengono ignorate come se il thread dell'interfaccia utente fosse bloccato.

Domande: il mio codice può essere risolto abbastanza facilmente? Se é cosi, come? Oppure, dovrei utilizzare un approccio diverso (ad esempio, BackgroundWorker)?

Codice (Form1 è una forma standard con un ProgressBar e un metodo pubblico, UpdateProgress, che imposta il valore del ProgressBar):

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

risposta

7

@ La risposta di StephenCleary è corretta. Tuttavia, ho dovuto apportare una piccola modifica alla sua risposta per ottenere il comportamento che penso che OP desideri.

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

se Go (avanzamento) genera un'eccezione, quindi _progressForm.Close() non verrà mai chiamato -> la finestra di dialogo modale verrà bloccata per sempre – Hiep

+0

Ed è meglio cambiare "_progressForm.Activated" in "_progressForm .Shown" perché _progressForm potrebbe essere attivato più volte durante la sua vita .. -> Vai (progresso) sarà chiamato più volte .. – Hiep

+0

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

18

Il async e await parole chiave non significano "eseguiti su uno sfondo filo." Ho un async/await intro on my blog che descrive cosa significano do. È necessario posizionare esplicitamente le operazioni associate alla CPU su un thread in background, ad esempio Task.Run.

Inoltre, la documentazione Task-based Asynchronous Pattern descrive gli approcci comuni con il codice async, ad esempio la segnalazione dell'avanzamento.

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

Nota che il tipo Progress<T> gestisce correttamente filo marshaling, quindi non c'è bisogno di marshalling all'interno Form1.UpdateProgress.

+0

Le modifiche non producono i risultati desiderati. Poiché OP è in esecuzione in un'applicazione 'Console', non esiste' SynchronizationContext'. Penso che sia necessario introdurre 'Progresso ' per funzionare correttamente? – YK1

+0

I componenti di Windows Form stabiliscono un 'WinFormsSynchronizationContext' quando vengono creati, e' Progress 'non richiede un' SynchronizationContext' (anche se funziona meglio con uno). –

+0

Inserisco un punto di interruzione nel delegato passato a 'Progress ' - non viene mai colpito - L'interfaccia utente si blocca solo mentre il lavoro sta girando. Pensavo che 'Application.Run()' fosse necessario per stabilire 'SynchronizationContext' -' _progressForm.Show' ne crea uno? Non sono sicuro. – 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); 
    }); 
} 

Correggetemi se sbaglio, ma questo sembra essere il modo più semplice per aggiornare una barra di avanzamento.