6

Escribo un programa de descarga en C# y me detengo en el siguiente problema: ¿qué tipo de método debo usar para paralelizar mis descargas y actualizar mi GUI?C# Downloader: ¿debería usar Threads, BackgroundWorker o ThreadPool?

En mi primer intento, utilicé 4 subprocesos y al finalizar cada uno de ellos comencé otro: el problema principal era que mi CPU funciona al 100% en cada nuevo inicio de subproceso.

Buscando en Google, encontré la existencia de BackgroundWorker y ThreadPool: afirmando que quiero actualizar mi GUI con el progreso de cada enlace que estoy descargando, ¿cuál es la mejor solución?

1) Creando 4 BackgroundWorker diferentes, adjuntando a cada evento ProgressChanged un Delegado a una función en mi GUI para actualizar el progreso?

2) ¿Utiliza ThreadPool y establece el número máximo y mínimo de hilos en el mismo valor?

Si elijo # 2, cuando no hay más hilos en la cola, ¿detiene los 4 hilos de trabajo? ¿Los suspende? Como tengo que descargar diferentes listas de enlaces (20 enlaces a cada uno de ellos) y pasar de uno a otro cuando se completa, ¿el ThreadPool inicia y detiene los hilos entre cada lista?

Si deseo cambiar el número de hilos de trabajo en vivo y decido usar ThreadPool, cambiando de 10 hilos a 6, ¿lanza y corta 4 hilos aleatorios?

Esta es la única parte que me está dando dolores de cabeza. Agradezco a cada uno de ustedes por adelantado sus respuestas.

+0

¿Por qué no utiliza hilos del Threadpool? http://msdn.microsoft.com/en-us/library/3dasc8as% 28v = vs.80% 29.aspx # Y23 – Stormenet

Respuesta

10

se recomienda usar WebClient.DownloadFileAsync para esto. Puede tener varias descargas funcionando, cada una de las cuales genera el evento DownloadProgressChanged a medida que avanza, y DownloadFileCompleted cuando termina.

Puede controlar la concurrencia utilizando una cola con un semáforo o, si está utilizando .NET 4.0, un BlockingCollection. Por ejemplo:

// Information used in callbacks. 
class DownloadArgs 
{ 
    public readonly string Url; 
    public readonly string Filename; 
    public readonly WebClient Client; 
    public DownloadArgs(string u, string f, WebClient c) 
    { 
     Url = u; 
     Filename = f; 
     Client = c; 
    } 
} 

const int MaxClients = 4; 

// create a queue that allows the max items 
BlockingCollection<WebClient> ClientQueue = new BlockingCollection<WebClient>(MaxClients); 

// queue of urls to be downloaded (unbounded) 
Queue<string> UrlQueue = new Queue<string>(); 

// create four WebClient instances and put them into the queue 
for (int i = 0; i < MaxClients; ++i) 
{ 
    var cli = new WebClient(); 
    cli.DownloadProgressChanged += DownloadProgressChanged; 
    cli.DownloadFileCompleted += DownloadFileCompleted; 
    ClientQueue.Add(cli); 
} 

// Fill the UrlQueue here 

// Now go until the UrlQueue is empty 
while (UrlQueue.Count > 0) 
{ 
    WebClient cli = ClientQueue.Take(); // blocks if there is no client available 
    string url = UrlQueue.Dequeue(); 
    string fname = CreateOutputFilename(url); // or however you get the output file name 
    cli.DownloadFileAsync(new Uri(url), fname, 
     new DownloadArgs(url, fname, cli)); 
} 


void DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e) 
{ 
    DownloadArgs args = (DownloadArgs)e.UserState; 
    // Do status updates for this download 
} 

void DownloadFileCompleted(object sender, AsyncCompletedEventArgs e) 
{ 
    DownloadArgs args = (DownloadArgs)e.UserState; 
    // do whatever UI updates 

    // now put this client back into the queue 
    ClientQueue.Add(args.Client); 
} 

No hay necesidad de administrar explícitamente los hilos o ir al TPL.

+0

Creo línea ClientQueue.Add (nuevo WebClient()); es incorrecto y debe ser ClientQueue.Add (cli). De todos modos, creo que hay 2 problemas con este método: 1) Tengo que especificar el nombre del archivo antes de descargarlo, pero no sé su nombre de antemano. Usualmente tomo el nombre del enlace ya sea de la "Content-Disposition" de la respuesta del encabezado http. 2) La primera vez que escribí mi aplicación, entre las opciones disponibles, estaba WebClient, pero si mal no recuerdo abre InternetExplorer en segundo plano para cada enlace. Todavía recuerdo esa ventana emergente que vino de la nada ... – DDB

+0

He reparado el error que identificaste. 'WebClient' no abre IE. Usted podría estar pensando en el control 'WebBrowser'. 'WebClient' es un contenedor alrededor de' HttpWebRequest' y 'HttpWebResponse'. Si necesita información de los encabezados, puede obtenerlos de la propiedad 'ResponseHeaders'. Lo anterior es solo un ejemplo. Sus requisitos se cumplen fácilmente haciendo algunos cambios menores. –

+0

Si bien el uso de subprocesos explícitos podría funcionar, es increíblemente inútil dedicar un hilo por cada descarga. Y el TPL podría no hacer un muy buen trabajo. En una buena conexión, puede tener una docena o más descargas simultáneas en ejecución, cada una de las cuales será un hilo individual que pasa la mayor parte del tiempo esperando datos. Contraste eso al uso de 'DownloadFileAsync', que asignará solo tantos hilos como sea necesario para manejar los datos mientras se descargan. –

0

Tener 100% de carga de CPU no tiene nada que ver con la descarga (ya que su red es prácticamente siempre el cuello de botella). Yo diría que tienes que verificar tu lógica de cómo esperas que se complete la descarga.

¿Puedes publicar un código del código del hilo que comiences varias veces?

+0

No tiene nada que ver con el código per se, pero el hecho de que la creación de un nuevo hilo use cpu (mentí sobre el 100 % cpu, es más 40-50% solo por el tiempo [instantáneo] para crear el hilo, va a la normalidad [Estoy en un viejo Turion64bit 1.8GHz, un solo núcleo, entonces noto estos abusos de la CPU) y crear y destruir hilos es un desperdicio de CPU y ram, ya que pueden reutilizarse: me gustaría saber cuál sería la "mejor" solución. – DDB

4

creo que usted debe buscar en el uso de la Task Parallel Library, que es nueva en .NET 4 y está diseñado para resolver este tipo de problemas

+0

¿Puedo enmendar esto con otra solución en la misma ruta? Un backgroundworker con un parallel.foreach (urls, url => {/ * do action * /}); en eso. - es más fácil de leer (como un foreach) y permite que la lógica continúe mientras se ejecuta el BGW. –

+0

Parece que MaxDegreeOfParallelism me permite establecer el número máximo de hilos/tareas pero no el número mínimo. No solo esto, sino que parece que no puedo cambiar este valor en vivo. Buena sugerencia, sin embargo. – DDB

0

Al crear 4 diferentes backgroundworkers será la creación de hilos separados que ya no interferirán con tu GUI. Los trabajadores de campo son simples de implementar y, según lo que entiendo, harán exactamente lo que usted necesita que hagan.

Personalmente, haría esto y simplemente permitiría que los otros no comiencen hasta que el anterior haya terminado. (O tal vez sólo uno, y permitir que se ejecute un método a la vez en el orden correcto.)

FYI - Backgroundworker

+0

El uso de 'BackgroundWorker' no crea un nuevo proceso, sino que se ejecuta en un subproceso de grupo de subprocesos. Él no creará procesos separados, sino que separará los hilos. –

+0

Editado para corregir. Tenía términos confusos – sealz