2010-03-02 3 views
2

J'ai une table d'URL que je dois parcourir, télécharger chaque fichier, mettre à jour la table et renvoyer les résultats. Je veux courir jusqu'à 10 téléchargements à un moment si je pense à l'aide de délégués comme suit:Exécution asynchrone de plusieurs délégués et attente de réponse en C#

DataTable photos; 
bool scanning = false, 
    complete = false; 
int rowCount = 0; 

public delegate int downloadFileDelegate(); 

public void page_load(){ 

    photos = Database.getData...   

    downloadFileDelegate d = downloadFile; 

    d.BeginInvoke(downloadFileComplete, d); 
    d.BeginInvoke(downloadFileComplete, d); 
    d.BeginInvoke(downloadFileComplete, d); 
    d.BeginInvoke(downloadFileComplete, d); 
    d.BeginInvoke(downloadFileComplete, d); 

    while(!complete){} 

    //handle results... 

} 

int downloadFile(){ 

    while(scanning){} scanning = true; 

    DataRow r; 

    for (int ii = 0; ii < rowCount; ii++) { 

     r = photos.Rows[ii]; 

     if ((string)r["status"] == "ready"){ 

      r["status"] = "running"; 

      scanning = false; return ii;     

     } 

     if ((string)r["status"] == "running"){ 

      scanning = false; return -2;     

     } 

    } 

    scanning = false; return -1;   

} 

void downloadFileComplete(IAsyncResult ar){ 

    if (ar == null){ return; } 

    downloadFileDelegate d = (downloadFileDelegate)ar.AsyncState; 

    int i = d.EndInvoke(ar); 

    if (i == -1){ complete = true; return; }  


    //download file... 

    //update row 
    DataRow r = photos.Rows[i]; 

    r["status"] = "complete"; 

    //invoke delegate again 
    d.BeginInvoke(downloadFileComplete, d); 

} 

Cependant quand je lance ce qu'il faut la même quantité de temps pour exécuter 5 comme il le fait 1. Je me attendais il faut prendre 5 fois plus vite.

Des idées?

+1

En tant que problème secondaire, votre code n'est pas thread-safe. – RichardOD

Répondre

4

On dirait que vous essayez d'utiliser lockless syncronization (en utilisant while(scanning) pour vérifier un booléen qui est fixé à le début de la fonction et la réinitialisation à la fin), mais tout ce que vous réussissez à faire est d'exécuter une récupération à la fois.

  1. Y a-t-il une raison pour laquelle vous ne voulez pas qu'ils s'exécutent simultanément? Cela semble être tout le point de votre exercice. Je ne vois pas de raison pour cela, donc je perdrais le drapeau scanning (et la logique associée) entièrement.
  2. Si vous allez prendre cette approche, vos drapeaux booléens doivent être déclarés comme volatile (sinon leur lectures pourraient être mises en cache et vous pourriez attendre indéfiniment)
  3. Vos opérations de mise à jour des données (mise à jour de la valeur dans le DataRow) doit avoir lieu sur le thread UI. Vous devrez encapsuler ces opérations dans un appel Control.Invoke ou Control.BeginInvoke, sinon vous interagirez avec le contrôle à travers les limites de fil.
  4. BeginInvoke renvoie AsyncWaitHandle. Utilisez ceci pour votre logique qui aura lieu lorsque les opérations seront toutes terminées. Quelque chose comme ça

-

WaitHandle[] handles = new WaitHandle[] 
{ 
    d.BeginInvoke(...), 
    d.BeginInvoke(...), 
    d.BeginInvoke(...), 
    d.BeginInvoke(...), 
    d.BeginInvoke(...) 
} 

WaitHandle.WaitAll(handles); 

Cela entraînera le thread appelant à bloquer jusqu'à ce que toutes les opérations sont terminées.

+0

ouais, il semble que le code essaie de sérialiser les opérations de téléchargement. –

+0

Je tiens à préciser que vous faites référence à (balayage) par opposition à tout (! Complet), comme initialement je confondais à propos de ce que vous faisiez référence. – RichardOD

+0

Pour que le code s'exécute simultanément, l'accès à DataTable doit être protégé. Alternativement, la logique asynchrone doit être factorisée à partir de la mise à jour de la structure de données partagée. – LBushkin

0

Cela prendra le même laps de temps si vous êtes limité par la bande passante réseau. Si vous téléchargez tous les 10 fichiers du même site, cela ne sera pas plus rapide. multi threading est utile lorsque vous soit voulez l'interface utilisateur pour répondre ou si vous avez processeur quelque chose noyaux intensif et plusieurs

+2

Dans certains cas, la latence finit par limiter le débit sur une seule connexion, et plusieurs téléchargements peuvent finir par être un peu plus rapides. – Yuliy

+0

Je suis d'accord avec Yuliy- Je reformulerais "ne sera pas plus rapide" à "pourrait ne pas être plus rapide" – RichardOD

0
WaitHandle[] handles = new WaitHandle[5]; 
handles[0] = d.BeginInvoke(downloadFileComplete, d).AsyncWaitHandle; 
handles[1] = d.BeginInvoke(downloadFileComplete, d).AsyncWaitHandle; 
handles[2] = d.BeginInvoke(downloadFileComplete, d).AsyncWaitHandle; 
handles[3] = d.BeginInvoke(downloadFileComplete, d).AsyncWaitHandle; 
handles[4] = d.BeginInvoke(downloadFileComplete, d).AsyncWaitHandle; 
WaitHandle.WaitAll(handles); 
+1

Ceci est une meilleure technique, mais ne fera rien pour augmenter la performance. – RichardOD

+0

Cela ne résout pas les problèmes qui provoquent réellement l'exécution incorrecte du code, pas les accès simultanés inappropriés aux données partagées. – LBushkin

0

De nombreux éléments de cette implémentation ne sont pas sûrs pour une utilisation simultanée.

Mais celui qui est susceptible de causer l'effet que vous décrivez est le fait que downloadFile() a une boucle while() qui examine la variable scanning. Cette variable est partagée par toutes les instances du délégué en cours d'exécution. Cette boucle while empêche les délégués de s'exécuter simultanément.

Cette « boucle de balayage » est pas une construction de filetage approprié - il est possible que deux threads à la fois lire la variable et à la fois il mis en même temps. Vous devez vraiment utiliser une instruction Semaphore ou lock() pour protéger le DataTable d'un accès simultané.Comme chaque thread passe le plus clair de son temps à attendre que scanning soit faux, la méthode downloadFilene peut pas exécuter simultanément.

Vous devriez reconsidérer la façon dont vous avez structuré ce code afin que le téléchargement des données et la mise à jour des structures dans votre application ne soient pas en conflit.

+0

Il semblerait que la "boucle de balayage" soit totalement inutile dans ce cas, sauf si quelque chose a été omis. –

+0

@Adam Robinson: L'objectif de la boucle d'analyse semble être de protéger le DataTable de l'accès simultané. Mais je peux me tromper - la structure du code rend difficile la déduction de l'intention. – LBushkin

0

Quelque chose comme ça fonctionnerait mieux je pense.

public class PhotoDownload 
{ 
    public ManualResetEvent Complete { get; private set; } 
    public Object RequireData { get; private set; } 
    public Object Result { get; private set; } 
} 

public void DownloadPhotos() 
{ 
    var photos = new List<PhotoDownload>(); 

    // build photo download list 

    foreach (var photo in photos) 
    { 
     ThreadPool.QueueUserWorkItem(DownloadPhoto, photo); 
    } 

    // wait for the downloads to complete 

    foreach (var photo in photos) 
    { 
     photo.Complete.WaitOne(); 
    } 

    // make sure everything happened correctly 
} 

public void DownloadPhoto(object state) 
{ 
    var photo = state as PhotoDownload; 
    try 
    { 
      // do not access fields in this class 
      // everything should be inside the photo object 
    } 
    finally 
    { 
     photo.Complete.Set(); 
    } 
} 
Questions connexes