Richiede Async / Attendi equivalente per BackgroundWorker

1

Dato il codice seguente nell'evento DoWork() di un oggetto BackgroundWorker , come può il concetto essere convertito nel modello Async/Await ?

Desidero eseguire più download contemporaneamente al fine di massimizzare la larghezza di banda. Dovrò anche mantenere la possibilità di incrementare una barra di avanzamento nella finestra di dialogo.

Dim oChunks As SortedDictionary(Of String, Byte())
Dim oFiles As List(Of FileInfo)
Dim iChunk As Integer

oChunks = New SortedDictionary(Of String, Byte())
oFiles = Service.Client.GetChunks(Me.Upload.UploadId, Me.Target)

oFiles.ForEach(Sub(File As FileInfo)
                 Worker.ReportProgress((iChunk / Me.Upload.Chunks) * 100, "Downloading...")
                 oChunks.Add(File.Name, Service.Client.GetChunk(File, Me.Target))
                 iChunk += 1
               End Sub)

Una risposta in C # è benvenuta; Posso tradurre.

Modifica

In base al codice di esempio di Ewan, ho trovato la versione seguente.

Ma non funziona correttamente:

  1. I download continuano ad essere eseguiti in sequenza, non in parallelo
  2. I blocchi dell'interfaccia utente durante l'esecuzione
  3. L'evento Download.Progress non viene attivato

Cosa mi manca?

Public Class Main
  Private Async Sub cmdSave_Click(Sender As Button, e As EventArgs) Handles cmdSave.Click
    Dim oChunks As SortedDictionary(Of String, Byte())
    Dim oFiles As List(Of FileInfo)
    Dim aData As Byte()

    Me.Upload = dgvUploads.CurrentRow.DataBoundItem

    pnlTarget.Enabled = False
    dlgSave.FileName = "{0}.zip".ToFormat(Me.Upload.UploadName)
    txtFile.Text = String.Empty

    If dlgSave.ShowDialog = DialogResult.OK Then
      Me.SetControlsEnabled(False)

      'bgwWorker.RunWorkerAsync(JobTypes.Save)

      oFiles = Await Me.GetFilesAsync(Me.Upload.UploadId, Me.Target)
      oChunks = Await Me.GetChunksAsync(oFiles)
      aData = Await Me.MergeChunksAsync(oChunks)

      File.WriteAllBytes(dlgSave.FileName, aData)

      Me.SetControlsEnabled(True)

      MsgBox("Download complete.", MsgBoxStyle.Information, Me.Text)
    End If
  End Sub



  Public Async Function GetFilesAsync(UploadId As Integer, Target As Targets) As Task(Of List(Of FileInfo))
    Dim oArgs As ProgressEventArgs

    oArgs = New ProgressEventArgs(0, "Initializing...", ProgressBarStyle.Marquee)
    Me.ReportProgress(Nothing, oArgs)

    Return Await Service.Client.GetFilesAsync(UploadId, Target)
  End Function



  Public Async Function GetChunksAsync(Files As List(Of FileInfo)) As Task(Of SortedDictionary(Of String, Byte()))
    Dim oDownload As Download
    Dim oChunks As SortedDictionary(Of String, Byte())
    Dim aChunks As Chunk()
    Dim oTasks As List(Of Task(Of Chunk))
    Dim oArgs As ProgressEventArgs

    prgProgress.Value = 0

    oArgs = New ProgressEventArgs(Files.Count, "Downloading...", ProgressBarStyle.Continuous)
    oTasks = New List(Of Task(Of Chunk))

    Files.ForEach(Sub(File)
                    oDownload = New Download

                    AddHandler oDownload.Progress, AddressOf ReportProgress

                    oTasks.Add(oDownload.GetChunkAsync(File, Me.Target, oArgs))
                  End Sub)

    aChunks = Await Task.WhenAll(oTasks.ToArray)
    oChunks = New SortedDictionary(Of String, Byte())

    aChunks.ToList.ForEach(Sub(Chunk)
                             oChunks.Add(Chunk.Name, Chunk.Data)
                           End Sub)

    Return oChunks
  End Function



  Public Async Function MergeChunksAsync(Chunks As SortedDictionary(Of String, Byte())) As Task(Of Byte())
    Dim oChunks As List(Of Byte())
    Dim iOffset As Integer
    Dim oArgs As ProgressEventArgs
    Dim aData As Byte()

    prgProgress.Value = 0

    oArgs = New ProgressEventArgs(Chunks.Count, "Saving...", ProgressBarStyle.Continuous)

    aData = Await Task.Run(Function()
                             oChunks = Chunks.Select(Function(Pair) Pair.Value).ToList

                             aData = New Byte(oChunks.Sum(Function(Chunk) Chunk.Length) - 1) {}

                             oChunks.ForEach(Sub(Chunk)
                                               Me.ReportProgress(Nothing, oArgs)

                                               Buffer.BlockCopy(Chunk, 0, aData, iOffset, Chunk.Length)

                                               iOffset += Chunk.Length
                                             End Sub)
                             Return aData
                           End Function)

    Return aData
  End Function



  Public Sub ReportProgress(Sender As Download, e As ProgressEventArgs)
    prgProgress.Maximum = e.Maximum
    prgProgress.Style = e.Style

    If e.Style = ProgressBarStyle.Marquee Then
      prgProgress.Value = 0
    Else
      prgProgress.Increment(1)
    End If

    lblStatus.Text = e.Status
  End Sub
End Class



Public Class Download
  Public Event Progress As EventHandler(Of ProgressEventArgs)

  Public Async Function GetChunkAsync(File As FileInfo, Target As Targets, Args As ProgressEventArgs) As Task(Of Chunk)
    Dim oChunk As Chunk

    oChunk = New Chunk
    oChunk.Name = File.Name
    oChunk.Data = Await Service.Client.GetChunkAsync(File, Target)

    RaiseEvent Progress(Me, Args)

    Return oChunk
  End Function
End Class



Public Class Chunk
  Public Property Name As String
  Public Property Data As Byte()
End Class



Public Class ProgressEventArgs
  Inherits EventArgs

  Public Sub New(Maximum As Integer, Status As String, Style As ProgressBarStyle)
    _Maximum = Maximum
    _Status = Status
    _Style = Style
  End Sub



  Public ReadOnly Property Maximum As Integer
  Public ReadOnly Property Status As String
  Public ReadOnly Property Style As ProgressBarStyle
End Class
    
posta InteXX 25.06.2016 - 12:33
fonte

1 risposta

1

Forse qualcosa del genere:

public class Download
    {
        public EventHandler<int> Progress;
        public async Task GetData(string url)
        {
            int percentDone = 0;
            foreach (var chunk in chunks)
            {
                //get data
                //write data to disk?
                if(Progress != null)
                {
                    Progress(this, percentDone);
                }
            }
        }
    }

    public class ManyDownloads
    {
        public void ReportProgress(object sender, int progress)
        {
            //update the UI
        }
        public List<Download> Downloads { get; set; }
        public void DownloadAll(List<string> Urls)
        {
            this.Downloads = new List<Download>();
            foreach(var url in Urls)
            {
                var d = new Download();
                d.Progress += ReportProgress;
                d.GetData(url); //dont await will run async
                this.Downloads.Add(d); //keep hold of the download object so you can refernce it in ReportProgress if needed
            }

        }
    }

se hai bisogno del valore di ritorno della funzione puoi aggiungere tutte le attività a un array e utilizzare attendere Task.WhenAll o Task.WaitAll

public async Task DownloadAll(List<string> Urls)
{
    List<Task<string>> tasks = new List<Task<string>>();

    this.Downloads = new List<Download>();
    foreach(var url in Urls)
    {
        var d = new Download();
        d.Progress += ReportProgress;
        tasks.Add(d.GetData(url)); //dont await will run async
    }
    //tasks are all running in threads at this point
    await Task.WhenAll<string>(tasks.ToArray()); //wait for all the tasks to complete before continuing

    foreach(var t in tasks)
    {
        //save data
        File.WriteAllText(t.Result, "filename.txt"); //todo::check for exceptions
    }
}

Aggiorna i commenti:

La confusione è che la parola chiave async sulla funzione GetData non aggiunge funzionalità allo pseudo codice. senza un'attesa all'interno di quella funzione, il codice in quella funzione viene eseguito in modo sincrono. (Ho aggiunto l'async perché supponevo che il codice effettivo del download includesse un'attesa.)

Tuttavia! in questo caso non ci interessa particolarmente il codice nella funzione. Vogliamo eseguire l'intera funzione più volte in parallelo. Se si chiama una funzione Task senza attendere nel metodo DownloadAll, viene creato e viene eseguito una nuova attività per ogni download.

Devi stare attento a non disporre di questi compiti prima che siano completati. Se hai appena eseguito il primo set di codice in un'app console, l'app avvia tutti i download, quindi termina e esce prima del completamento.

    
risposta data 25.06.2016 - 13:45
fonte

Leggi altre domande sui tag