2016-10-21 2 views
0

Я снова занимаюсь поиском асинхронных задач. Нет mater, как я устанавливал Задачи, мое приложение постоянно терпит от UI. У меня есть следующий код для загрузки строки с веб-страницы:Async/await, запускайте блокировку пользовательского интерфейса без видимой причины

internal string DownloadString(string URL) 
{ 
     var result = LoadCompanyContracts(URL); 
     return result.Result; 
} 

internal async Task<string> LoadCompanyContracts(string URL) 
{ 
Task<string> task2 = Task<string>.Factory.StartNew(() => 
{ 
    for (int i = 0; i <= 10000000; i++) Console.WriteLine(i); 
    WebClient wc = new WebClient(); 
    string tmp = wc.DownloadString(new Uri(URL)); 
    return tmp; 
}); 
return task2.Result; 

} 

Когда я выполнить эту задачу и во время цикла для пользовательского интерфейса моего приложения замораживания. Хотя я считаю, что этот код не должен замораживать пользовательский интерфейс, я не могу найти решение. Я пробовал много разных опций и действительно хочу использовать задачи вместо потоков или событий с асинхронным WebClient.

Информация: Я использую .net 4.5 для своего проекта. Разница в моем коде заключается в том, что эти функции находятся внутри библиотеки классов (не знаю, имеет ли значение).

Возможно ли запустить этот код без блокировки пользовательского интерфейса с асинхронным ожиданием, вызвав функцию DownloadString из моего кода? Если нет, то какие альтернативы (любые хорошие пакеты nuget)?

+1

'result.Result' this blocks –

+2

Это неправильный способ использования' async' и Task.Result. –

+0

Интересно, что сам компилятор даст вам предупреждение, в котором говорится, что 'LoadCompanyContracts' будет работать синхронно. –

ответ

5

async ключевое слово не сделать что-то работать в асинхронном режиме, он позволяет использовать await в ждут уже асинхронной операции. Вы должны использовать DownloadStringTaskAsync действительно загрузить в асинхронном режиме:

internal async Task<string> LoadCompanyContracts(string URL) 
{ 
    .... 
    using(var wc = new WebClient()) 
    { 
     string tmp = await wc.DownloadStringTaskAsync(new Uri(URL)); 
     return tmp; 
    } 
} 

await самих по себе возвращает выполнение в контексте выполнения оригинала (то есть в потоке пользовательского интерфейса). Это может или не может быть нежелательно, поэтому код библиотеки обычно использует ConfigureAwait(false); и позволяет конечному пользователю библиотеки, чтобы решить, как ждать:

string tmp = await wc.DownloadStringTaskAsync(new Uri(URL)) 
        .ConfigureAwait(false); 

Наконец, нет никакого смысла в ожидании, если вы собираетесь вызовите .Result из функции верхнего уровня. Нет смысла использовать await вообще, если вы не хотите использовать результат метода в своем коде. LoadCompanyContracts может быть просто:

internal Task<string> LoadCompanyContracts(string URL) 
{ 
    .... 
    using(var wc = new WebClient()) 
    { 
     return wc.DownloadStringTaskAsync(new Uri(URL)) 
       .ConfigureAwait(false); 
    } 
} 

Нам

Как правило, вам не нужно использовать поджидают на всех, если вы просто возвращает результат асинхронной операции. Метод мог бы только return wc.DownloadStringTaskAsync(..);НО НОТА, который заставляет метод возвращать и перед тем, как закончите загрузку, удалите WebClient. Избежать блока using также не является решением, так как это позволит дорогостоящему объекту, например WebClient, дольше, чем необходимо.

Вот почему HttpClient предпочтительнее WebClient: один экземпляр поддерживает несколько одновременных вызовов, что означает, что вы можете иметь только один экземпляр, например, как поле и использовать его, например:

HttpClient _myClient =new HttpClient(); 

    internal Task<string> LoadCompanyContractsAsync(string URL) 
    { 
     .... 
     return _myClient.GetStringAsync(new Uri(URL)) 
         .ConfigureAwait(false); 
    } 
} 

Вы можете избавиться от ваш DownloadString, так как он ничего не делает сверху LoadCompanyContracts.Если он использует результат LoadCompanyContracts, следует переписать в виде:

internal async Task<string> DownloadString(string URL) 
{ 
    var result = await LoadCompanyContracts(URL); 
    //Do something with the result 
    return result; 
} 

EDIT

оригинальный ответ используется DownloadStringAsync который является унаследованным методом, который вызывает событие, когда загрузка завершится. Правильный метод DownloadStringTaskAsync

EDIT 2 Поскольку мы говорим о UI, код может быть асинхронными весь путь к началу обработчика событий, используя синтаксис async void для обработчика, например async void Button1_Click, например:

async void LoadCustomers_Click(...) 
{ 
    var contracts=await LoaCompanyContracts(_companyUrls); 
    txtContracts>Text=contracts; 
} 

в этом случае мы хотим , чтобы вернуться к исходной теме, поэтому мы не используем ConfigureAwait(false);

+0

Вы также можете использовать wait.Run (() => wc.DownloadString (...)), чтобы заставить действие выполнить фоновый поток. –

+0

Hi Panagioti, оба решения возвращают ошибку «Не могу дождаться пустоты». Возможно, вы имели в виду строку tmp = wait wc.DownloadStringTaskAsync (новый Uri (URL)); ? –

+1

@Evangelink Это просто потеряло бы поток. 'DownloadStringAsync' * работает * в фоновом потоке или, скорее, использует порты ввода IO, поэтому он не тратит нить * на все * во время ожидания результата. 'Task.Run', с другой стороны, тратит поток при ожидании ответа. –

 Смежные вопросы

  • Нет связанных вопросов^_^