2011-07-17 16 views
9

Tengo estos dos métodos, que quiero ejecutar de forma sincronizada para mantener la UI en buen estado. Sin embargo, todavía está colgando la IU. ¿Alguna sugerencia?C# métodos asíncronos todavía cuelgan UI

async void DoScrape() 
    { 
     var feed = new Feed(); 

     var results = await feed.GetList(); 
     foreach (var itemObject in results) 
     { 
      var item = new ListViewItem(itemObject.Title); 
      item.SubItems.Add(itemObject.Link); 
      item.SubItems.Add(itemObject.Description); 
      LstResults.Items.Add(item); 
     } 
    } 


    public class Feed 
    { 
     async public Task<List<ItemObject>> GetList() 
     { 
      var client = new WebClient(); 
      string content = await client.DownloadStringTaskAsync(new Uri("anyUrl")); 
      var lstItemObjects = new List<ItemObject>(); 
      var feed = new XmlDocument(); 
      feed.LoadXml(content); 
      var nodes = feed.GetElementsByTagName("item"); 

      foreach (XmlNode node in nodes) 
      { 
       var tmpItemObject = new ItemObject(); 
       var title = node["title"]; 
       if (title != null) tmpItemObject.Title = title.InnerText; 
       var link = node["link"]; 
       if (link != null) tmpItemObject.Link = link.InnerText; 
       var description = node["description"]; 
       if (description != null) tmpItemObject.Description = description.InnerText; 
       lstItemObjects.Add(tmpItemObject); 
      } 
      return lstItemObjects; 
     } 
    } 
+0

¿Y cómo llama a sus métodos asíncronos? Sospecho que los llamas y luego acceden a su resultado de forma bloqueante. – CodesInChaos

+0

DoScrape(); dentro de un botón. –

+0

¿Estás usando el hilo de fondo? ¿Qué estás usando ... WPF/Silverlight/WinForms? – sgtz

Respuesta

12

sospecho DownloadStringTaskAsync basa en HttpWebRequest.BeginGetResponse en un nivel inferior. Siendo este el caso, se sabe que la configuración para una webrequest no es completamente asincrónica. De forma molesta (y, francamente, estúpidamente), la fase de búsqueda DNS de una WebRequest asincrónica se realiza de forma síncrona y, por lo tanto, bloquea. Sospecho que este podría ser el problema que estás observando.

A continuación se reproduce un aviso en la documentación:

The BeginGetResponse method requires some synchronous setup tasks to complete (DNS resolution, proxy detection, and TCP socket connection, for example) before this method becomes asynchronous. As a result, this method should never be called on a user interface (UI) thread because it might take some time, typically several seconds. In some environments where the webproxy scripts are not configured properly, this can take 60 seconds or more. The default value for the downloadTime attribute on the config file element is one minute which accounts for most of the potential time delay.

Tienes dos opciones:

  1. Start la solicitud de un subproceso de trabajo (y en condiciones de alta carga , corre el riesgo de morir de hambre en ThreadPool debido a un comportamiento de bloqueo)
  2. (Tenuously) Realiza un programa ic Búsqueda DNS antes de activar la solicitud. Este puede hacerse de forma asíncrona. Es de esperar que la solicitud utilice la búsqueda en caché de DNS.

Fuimos para la tercera (y costosa) opción de implementar nuestra propia biblioteca HTTP adecuadamente asíncrona para obtener rendimiento decente, pero es probablemente un poco extremo, en su caso;)

+0

parece una buena explicación. pero no ayudó en mi caso. Trasladé la llamada HttpClient.GetByteArrayAsync de una función asíncrona en el hilo de UI a un trabajador de subprocesos. – yash

4

cuántos elementos hay que añadir en su vista de lista?

A menos que tome medidas para evitarlo, la vista de lista de WinForms hará un lote de procesamiento cada vez que agregue un elemento a la lista. Esto puede llevar tanto tiempo que agregar solo 100 elementos puede demorar varios segundos.

Pruebe usar BeginUpdate y EndUpdate alrededor de su ciclo para diferir la contabilidad de ListView hasta que termine.

async void DoScrape() 
{ 
    var feed = new Feed(); 

    var results = await feed.GetList(); 
    LstResults.BeginUpdate(); // Add this 
    try 
    { 
     foreach (var itemObject in results) 
     { 
      var item = new ListViewItem(itemObject.Title); 
      item.SubItems.Add(itemObject.Link); 
      item.SubItems.Add(itemObject.Description); 
      LstResults.Items.Add(item); 
     } 
    } 
    finally 
    { 
     LstResults.EndUpdate(); 
    } 
} 

Tengo que intentar finalmente para evitar todo tipo de dolor si hay una excepción.

+0

Eso no funcionó. Estaba siguiendo esta guía http://msdn.microsoft.com/en-us/vstudio/gg440604 y mis métodos no son muy diferentes de esta guía. Simplemente no entiendo. –

5

Pareces estar confundiendo asincrónico con paralelo. Ambos están basados ​​en Tareas, pero son completamente diferentes. No asuma que los métodos async se ejecutan en paralelo; no es así.

defecto asíncronas para trabajar en el mismo hilo , a menos que existan razones que fuerzan el motor asíncrono para hacer girar un nuevo hilo, como es el caso cuando el hilo principal no tiene un suministro de mensajes. Pero, en general, tiendo a pensar que la palabra clave async se ejecuta en el mismo hilo.

Utiliza WinForms, por lo que el subproceso de la interfaz de usuario tiene una bomba de mensajes. Por lo tanto, todo su código anterior se ejecuta en el subproceso UI.

Debe comprender que tiene NO introdujo cualquier paralelismo aquí.Lo que ha introducido a través de la palabra clave async es operaciones asincrónicas, NOT paralelo. No ha hecho nada para "hacer que su UI responda", excepto por esa llamada al DownloadStringTaskAsync que no lo forzará a esperar a que lleguen los datos, pero STILL tiene que hacer todo el procesamiento de la red (búsqueda DNS, etc.) en el hilo de la interfaz de usuario: aquí está la operación asíncrona en juego (esencialmente "guardas" el tiempo esperando las descargas).

Para mantener la respuesta de la interfaz de usuario, debe separar el trabajo que consume mucho tiempo en un subproceso separado mientras mantiene libre la interfaz de usuario. No está haciendo esto con la palabra clave async.

Debe usar Task.Factory.StartNew(...) para girar explícitamente un nuevo hilo para hacer el procesamiento en segundo plano.

+0

Pero me parece que la descarga es la única operación que consume mucho tiempo en su código. Un poco de análisis xml y la actualización de la interfaz de usuario es muy barato. La explicación de @ spender suena más probable. – CodesInChaos

+0

@CodeInChaos, cierto. Sin embargo, solo trato de aclarar que asincrónico NO es paralelo. Por lo tanto, todo lo que se hace con "esperar" se realiza en realidad en el hilo de la interfaz de usuario. Eso es INCLUYENDO la búsqueda DNS y otro acceso a la red. Lo que significa que solo el tiempo de espera de los datos para llegar se guarda con el código asíncrono, no con otra cosa. Editaré mi respuesta para ser más específico. –

Cuestiones relacionadas