C # Progress Bar Probleem melden/delegeren

Ik stuur bestanden over en wil een voortgangsbalk om de daadwerkelijke voortgang van elk bestand te laten zien. Dit werkt prima voor bestanden onder de 15 meg maar bestanden groter dan dat lijkt mijn applicatie te laten bevriezen. Als ik deze code niet voor de voortgangsbalk noem, worden deze grotere bestanden prima overgedragen.

Ik heb allerlei verschillende manieren geprobeerd om dit met afgevaardigden af ​​te handelen maar geen geluk. In plaats daarvan werken ze prima met kleinere bestanden, maar niet met grotere bestanden.

Enkele voorbeelden die hebben gewerkt ...

pbFileProgress.Invoke((MethodInvoker)
   delegate 
   { 
      pbFileProgress.Value = args.PercentDone;
   });                

Ook werkte deze verzameling methoden voor kleinere bestanden.

private delegate void SetProgressBarCallback(int percentDone);

public void UpdateProgressBar(object send, UploadProgressArgs args)
{
   if (pbFileProgress.InvokeRequired)
   {
      var d = new SetProgressBarCallback(ProgressBarUpdate);
      BeginInvoke(d, new object[] { args.PercentDone });
   }
   else
   {
      ProgressBarUpdate(args.PercentDone);
   }
}

public void ProgressBarUpdate(int percentDone)
{
   pbFileProgress.Value = percentDone;
}

Maar nogmaals, alles bevriest gewoon als ik grotere bestanden probeer.

4
Vermeld de code waar u het percentage hebt berekend dat is voltooid. Omdat uw probleem gerelateerd is aan de grootte van de download, probeert u waarschijnlijk de voortgangsbalk te vaak bij te werken (u downloadt bijvoorbeeld 10 bytes per keer en werkt de voortgangsbalk elke keer bij, dit zou goed werken voor een 100 byte-bestand, maar falen voor een bestand van 15 MB).
toegevoegd de auteur MusiGenesis, de bron
@AaronMcIver: ik heb verzonnen nummers gebruikt om het potentiële probleem te illustreren. Hopelijk zou niemand ooit proberen 10 bytes tegelijk te downloaden.
toegevoegd de auteur MusiGenesis, de bron
Waar is de code die het bestand kopieert? En hoe doe je het rijgen?
toegevoegd de auteur David Heffernan, de bron
Dit is een neveneffect van StartInvoke() te vaak aanroepen, de UI-thread komt er niet meer toe om zijn taken met lage prioriteit uit te voeren. Zoals schilderen en reageren op gebruikersinvoer. Geef gas zodat het niet vaker gebeurt dan ongeveer 25 keer per seconde. Ziet er glad uit voor het menselijk oog.
toegevoegd de auteur Hans Passant, de bron
@MusiGenesis Het OP heeft bestanden opgegeven onder 15MB, wat hopelijk meer dan 100 bytes zou impliceren maar toch de oorzaak zou kunnen zijn.
toegevoegd de auteur Aaron McIver, de bron
Enige reden waarom u de BackgroundWorker niet gebruikt? Deze functionaliteit bestaat binnen die klasse en maakt wat u triviaal probeert te doen. Om niet te zeggen dat het niet kan zonder de BackgroundWorker, ik probeer gewoon te begrijpen waarom?
toegevoegd de auteur Aaron McIver, de bron
Ik heb de code niet opgenomen voor de eigenlijke bestandsoverdracht, omdat het allemaal de UploadProgressEvent van een TransferRequest (Amazon S3-spul) instelt op de methode.
toegevoegd de auteur user1078155, de bron

3 antwoord

Ondanks het gebrek aan context, hier is een voorbeeld dat werkt. De methode BeginInvoke of Invoke wordt slechts 100 keer max. Genoemd.

Task.Factory.StartNew(() =>
   {
      using (var source = File.OpenRead(@"D:\Temp\bbe.wav"))
      using (var destination = File.Create(@"D:\Temp\Copy.wav"))
      {
         var blockUnit = source.Length/100;

         var total = 0L;
         var lastValue = 0;

         var buffer = new byte[4096];
         int count;

         while ((count = source.Read(buffer, 0, buffer.Length)) > 0)
         {
            destination.Write(buffer, 0, count);

            total += count;

            if (blockUnit > 0 && total/blockUnit > lastValue)
            {
               this.BeginInvoke(
                  new Action(value => this.progressBar1.Value = value),
                  lastValue = (int)(total/blockUnit));
            }
         }

         this.BeginInvoke(
            new Action(value => this.progressBar1.Value = value), 100);
      }
   });
2
toegevoegd

Dit probleem komt vaak voor bij het communiceren tussen threads op de achtergrond en op de voorgrond: de achtergrondthread verzendt de te veel updates van de voorgronddraad.

De draad op de voorgrond verwerkt de update, tekening en gebruikersinvoer, dus wanneer er te veel updates binnenkomen, bevriest de gebruikersinterface om in te halen.
Het is duidelijk dat, als de achtergronddraad updates blijft verzenden, van de voorgrond een back-up kan worden gemaakt, zelfs na wanneer de achtergrondtaak is voltooid!

Er zijn verschillende oplossingen voor dit probleem, maar mijn sterkste aanbeveling is om een ​​ Timer in de voorgrondthread te gebruiken voor enquête om de voortgang van de achtergrond te volgen en de UI te updaten.
Het voordeel van het gebruik van een Timer :

  • De achtergrondthread kan de voortgang zo vaak rapporteren als nodig
  • De draad van de voorgrond kan ontspannen totdat er een update nodig is
  • De draad van de voorgrond maakt geen back-up van updates
  • Als de voorgronddraad "rust", krijgt de achtergronddraad meer processortijd
  • De frequentie van de Timer kan worden ingesteld op een "redelijke" waarde, zoals 250ms (4 updates per seconde), zodat de voortgang soepel verloopt maar niet de hele processor in beslag neemt

Zoals altijd is de veiligheid van threads belangrijk bij het communiceren van de voortgang tussen threads. Het gebruik van een eenvoudige 32-bits int waarde is in dit scenario thread-safe, maar het gebruik van een 64-bit double is niet thread safe op 32-bit machines.

1
toegevoegd

Je zou kunnen Invoke op basis van het UI-element. Bijvoorbeeld:

private delegate void InvokeUpdateProgressBar(object send, UploadProgressArgs args);
private int _PercentDone = -1;

public void UpdateProgressBar(object send, UploadProgressArgs args)
{
   if(_PercentDone != args.PercentDone)
   {
      if (pbFileProgress.InvokeRequired)
      {
         pbFileProgress.Invoke(
            new InvokeUpdateProgressBar(UpdateProgressBar),
            new object[] { send, args });
      }
      else
      {
         ProgressBarUpdate(args.PercentDone);
      }
      _PercentDone = args.PercentDone;
   }
}

Anders zou ik voorstellen wat Aaron McIver deed en het BackgroundWorker klasse. Zie het voorbeeld hier voor meer informatie over het bijwerken van een voortgangsbalk met behulp van de klasse BackgroundWorker

Update Looks like you are not the only one with this issue. See Amazon s3 Transferutility.Upload hangs in C# here. Kent also points says: If you read in about the S3 forums you'll find many people having similar issues

0
toegevoegd
@ user1078155 - Ik zie dat u een int gebruikt als de PercentDone . Hoe zit het met het plaatsen van een vinkje in de functie UpdateProgressBar om te zien of deze waarde daadwerkelijk is gewijzigd. Ik weet niet zeker hoe u de PercentDone berekent, maar als het niet verandert, zou het u wat verwerking besparen als u Invoke niet aanroept. Zie mijn bijgewerkte voorbeeld.
toegevoegd de auteur SwDevMan81, de bron
Ik heb deze methode precies geprobeerd, geen dobbelstenen, hetzelfde probleem. App blokkeert bij grotere bestanden. Ik ben niet duidelijk hoe ik de klasse BackgroundWorker moet implementeren, om te integreren met de Amazon S3 TransferUtiltyUploadRequest.UploadProgressEvent-methode. Mijn excuses voor het feit dat ik niet duidelijk ben over dit alles ... Ik heb dagenlang met mijn hoofd geslagen over ditzelfde probleem en kan er geen oplossing voor vinden, in plaats van gewoon de voortgangsbalk helemaal weg te doen ... waar ik een hekel aan heb, vind ik het niet leuk om te zien dat de app de bestanden nog steeds verwerkt.
toegevoegd de auteur user1078155, de bron
Ik stopte dat in, het is logisch om de voortgangsbalk niet te laten bijwerken tenzij het percentage gedaan is veranderd. Maar het lost het probleem niet op: klein bestand werkt prima, groot bestand bevriest alles.
toegevoegd de auteur user1078155, de bron