2010-05-02 7 views
0

Aight, a fait un peu de googling et de recherche ici, la seule question que j'ai trouvé liée était this, bien que la seule réponse, il n'a pas été marqué comme accepté, est vieux et est source de confusion.UploadFileAsync non asynchrone?

Mon problème est fondamentalement ce que j'ai dit dans le titre. Ce qui se passe, c'est que l'interface graphique se bloque pendant le téléchargement. Mon code:

// stuff above snipped 

public partial class Form1 : Form 
{ 
    WebClient wcUploader = new WebClient(); 

    public Form1() 
    { 
     InitializeComponent(); 

     wcUploader.UploadFileCompleted += new UploadFileCompletedEventHandler(UploadFileCompletedCallback); 
     wcUploader.UploadProgressChanged += new UploadProgressChangedEventHandler(UploadProgressCallback); 
    } 

    private void button1_Click(object sender, EventArgs e) 
    { 
     if (openFileDialog1.ShowDialog() == DialogResult.OK) 
     { 
      string toUpload = openFileDialog1.FileName; 
      wcUploader.UploadFileAsync(new Uri("http://anyhub.net/api/upload"), "POST", toUpload); 
     } 
    } 

    void UploadFileCompletedCallback(object sender, UploadFileCompletedEventArgs e) 
    { 
     textBox1.Text = System.Text.Encoding.UTF8.GetString(e.Result); 
    } 

    void UploadProgressCallback(object sender, UploadProgressChangedEventArgs e) 
    { 
     textBox1.Text = (string)e.UserState + "\n\n" 
      + "Uploaded " + e.BytesSent + "/" + e.TotalBytesToSend + "b (" + e.ProgressPercentage + "%)"; 
    } 
} 

EDIT: Pour plus de précisions, voici ce qui se passe dans l'ordre:

  1. je clique button1
  2. Je sélectionnez un fichier
  3. L'interface graphique cesse de répondre, comme dans quand je clique dessus, rien ne se passe
  4. Après quelques secondes, 50% apparaît dans la zone de texte En outre, les résultats de la réalisation. Voir mon commentaire à la question que je Marquée comme la solution
  5. Après une seconde ou avec l'interface graphique ne répond pas en entre elle est remplacée par la réponse
+2

* Pourquoi * pensez-vous que ce n'est pas asynchrone? –

+0

Oups. Ajouté – unrelativity

Répondre

4

Bien sûr, il est.

Le code fonctionne très bien.

wcUploader.UploadFileAsync(...) initie la requête et l'exécution continue, pendant ce temps la progression est mise à jour dans TextBox1 et à la fin j'obtiens du JSON.

C'est Async. Si vous appelez simplement wcUploader.UploadFile, l'exécution bloquera jusqu'à ce que le fichier ait été téléchargé et vous n'obtiendrez aucun événement de progression.

Bottom line:

L'interface utilisateur est pas bloqué, les progrès événements sont appelés et l'interface utilisateur est mis à jour en temps réel.

Mise à jour:

Pour éliminer le bloc initial lorsque le webclient établit la connexion http, il suffit d'appeler le téléchargement sur un autre fil. Dans ce scénario, vous devez utiliser l'invocation pour éviter des exceptions de fil croisées:

using System; 
using System.Net; 
using System.Text; 
using System.Threading; 
using System.Windows.Forms; 

namespace WindowsFormsApplication1 
{ 
    public partial class Form1 : Form 
    { 
     private readonly WebClient wcUploader = new WebClient(); 

     public Form1() 
     { 
      InitializeComponent(); 

      wcUploader.UploadFileCompleted += UploadFileCompletedCallback; 
      wcUploader.UploadProgressChanged += UploadProgressCallback; 
     } 


     private void UploadFileCompletedCallback(object sender, UploadFileCompletedEventArgs e) 
     { 
      // a clever way to handle cross-thread calls and avoid the dreaded 
      // "Cross-thread operation not valid: Control 'textBox1' accessed 
      // from a thread other than the thread it was created on." exception 

      // this will always be called from another thread, 
      // no need to check for InvokeRequired 
      BeginInvoke(
       new MethodInvoker(() => 
        { 
         textBox1.Text = Encoding.UTF8.GetString(e.Result); 
         button1.Enabled = true; 
        })); 
     } 

     private void UploadProgressCallback(object sender, UploadProgressChangedEventArgs e) 
     { 
      // a clever way to handle cross-thread calls and avoid the dreaded 
      // "Cross-thread operation not valid: Control 'textBox1' accessed 
      // from a thread other than the thread it was created on." exception 

      // this will always be called from another thread, 
      // no need to check for InvokeRequired 

      BeginInvoke(
       new MethodInvoker(() => 
        { 
         textBox1.Text = (string)e.UserState + "\n\n" 
             + "Uploaded " + e.BytesSent + "/" + e.TotalBytesToSend 
             + "b (" + e.ProgressPercentage + "%)"; 
        })); 
     } 

     private void button1_Click(object sender, EventArgs e) 
     { 
      textBox1.Text = ""; 

      if (openFileDialog1.ShowDialog() == DialogResult.OK) 
      { 
       button1.Enabled = false; 
       string toUpload = openFileDialog1.FileName; 
       textBox1.Text = "Initiating connection"; 
       new Thread(() => 
          wcUploader.UploadFileAsync(new Uri("http://anyhub.net/api/upload"), "POST", toUpload)).Start(); 
      } 
     } 
    } 
} 
+0

Oh. Oh. Ohh. La raison pour laquelle je pensais que ce n'était pas asynchrone était parce que le fichier que je choisissais de télécharger était si minuscule qu'il n'y avait pratiquement aucune opportunité ... mais il y en a, et mon esprit a rapidement passé cette apparence de progrès comme un bogue. L'interface graphique * se bloque, mais cela ne dure que quelques secondes entre le début et la fin du téléchargement. – unrelativity

+0

Vous pouvez vous débarrasser de la pause initiale en faisant l'appel 'wcUploader.UploadFileAsync' dans un autre thread. Je vais ajouter cela à ma réponse, MAIS - alors vous devez utiliser un modèle d'invocation comme mentionné par eamon. –

+0

Merci pour cela, reviendrait encore une fois si je pouvais, ah bien. :) – unrelativity

1

Il y a un bug dans votre code qui vaut la fixation de toute façon, quel que soit le verrouillage de l'interface utilisateur:

Vous spécifiez deux callbacks qui asynchrone l'uploader devrait se déclencher. Dans ces rappels, vous serez en cours d'exécution sur le fil de l'uploader; Cependant, vous ne pouvez toucher l'interface graphique que depuis le thread principal de l'interface graphique. Vos rappels peuvent donc corrompre l'état de l'interface graphique.

Vous ne devez pas toucher textBox1.Text dans les deux rappels. Il est peu probable que ce soit le problème, mais néanmoins, vous devriez le réparer pour éviter les bugs de crash et de corruption. La question que vous avez liée illustre une façon d'éviter cela: vérifier la propriété Control.InvokeRequired du formulaire (dans les coulisses, cela vérifie si vous êtes sur le bon thread), ou simplement supposer qu'une invocation est requise et ensuite - utiliser Control.BeginInvoke pour déclencher une méthode sur le fil de l'interface graphique.

N'importe lequel de vos contrôles fera car ils s'exécutent tous dans le même thread; donc if (textBox1.InvokeRequired) textBox1.BeginInvoke... est aussi bon que if (this.InvokeRequired) this.BeginInvoke...

+0

Le code original, tel qu'il a été publié, peut sembler avoir besoin d'être invoqué, mais ne l'est pas réellement. Le code mis à jour que j'ai posté, cependant. En outre, vous ne devriez jamais vérifier InvokeRequired lorsque vous n'êtes pas sûr de la source de l'appel. Dans l'exemple donné, les appels vont toujours venir du même endroit, donc la vérification est inutile. –

+0

Pourquoi le code d'origine serait-il sûr? Les rappels webclient sont-ils exécutés sur le thread d'origine? Où est-ce documenté? –

+0

Au lieu de répondre à votre question comme demandé, laissez-moi vous poser quelques questions: Avez-vous essayé d'exécuter le code? Avez-vous écrit du code en utilisant des appels asynchrones avec WebClient? Je vais deviner que la réponse est non. Avez-vous fait des suppositions sur le comportement de la classe WebClient? Je dois supposer que la réponse est oui. Je vais laisser le reste comme un exercice. bravo .. –