С#: блокировка вызова функции до тех пор, пока не будет выполнено условие

Я разрабатываю приложение С# Winforms, часть приложения будет загружать файлы на веб-сервер с помощью AsyncUpload (используя его из-за необходимости использовать обратный вызов porgress), в программе С#

у меня есть простой цикл for, который вызывает функцию загрузки

 for(int i=0;i < 10 ; i++)
{
  Uploadfun();
}

И веселье творит чудеса:

Uploadfun()
  { 
  // Logic comes here

   // webClient.UploadFileAsync runs a 2nd thread to perform upload .. 
   webClient.UploadFileAsync(uri, "PUT", fileNameOnHD);  

 }

И обратный вызов, который вызывается после завершения асинхронной загрузки.

Upload_Completed_callback()
{
  //Callback event
}

Изменить

Логическая последовательность:

  1. Веселье вызывается (из цикла)
  2. Веселая логика выполнена и сделано..
  3. Возвращается к циклу for
  4. Обратный вызов будет вызван в конце концов, когда UploadFileAsync (который выполняет некоторую логику в другом потоке) завершится

Проблема в 3-й точке, когда выполнение возвращается к циклу for, мне нужно заблокировать продолжение цикла до тех пор, пока не будет вызван обратный вызов.


person Madi D.    schedule 06.02.2010    source источник
comment
У вас есть доступ к реализации fun? Вероятно, вам следует подумать о предоставлении синхронного интерфейса, на котором реализован асинхронный API.   -  person mmx    schedule 06.02.2010
comment
Как вы это описываете, ваш код кажется последовательным и однопоточным, а это означает, что до тех пор, пока функция callback не будет вызвана, цикл не будет продолжаться. Пожалуйста, поправьте меня, если я ошибаюсь, дав более подробное объяснение.   -  person Darin Dimitrov    schedule 06.02.2010
comment
@Madi: Тогда ты делаешь это в обратном порядке. Вы должны использовать синхронную версию UploadFile в основе логики и использовать асинхронный API поверх нее, если вам это нужно.   -  person mmx    schedule 06.02.2010
comment
@Mehrdad: на самом деле я вынужден использовать асинхронную версию, потому что она предоставляет мне обратные вызовы прогресса, что является требованием.   -  person Madi D.    schedule 06.02.2010


Ответы (4)


Итак, если я правильно понимаю, вы хотите вызвать UploadFileAsync, а затем заблокировать, пока асинхронный вызов не достигнет вашего обратного вызова. Если это так, я бы использовал AutoResetEvent т.е.

private readonly AutoResetEvent _signal = new AutoResetEvent(false); 

fun()
  { 
  // Logic comes here

   // runs a 2nd thread to perform upload .. calling "callback()" when done
   webClient.UploadFileAsync(uri, "PUT", fileNameOnHD);  

   _signal.WaitOne();   // wait for the async call to complete and hit the callback     
 }



callback()
 {
   //Callback event
   _signal.Set(); // signal that the async upload completed
 }

Использование AutoResetEvent означает, что состояние автоматически сбрасывается после вызова Set и ожидающий поток получает сигнал через WaitOne.

person zebrabox    schedule 06.02.2010
comment
при использовании Waitone() после uploadAsync программа останавливается, а обратный вызов не вызывается. - person Madi D.; 06.02.2010
comment
хорошо.. я исправил проблему с остановкой, вызванную fun() в отдельном потоке, очевидно, вызов ее в основном потоке вызывает проблему!, @Juliet Kinda помогла указать на проблему.. спасибо вам обоим =) - person Madi D.; 07.02.2010

В С# методы блокируются по умолчанию, поэтому вам не нужно ничего делать. Я предполагаю, что по какой-то причине вы вызываете неблокирующий метод, который запускает фоновую задачу/поток/что угодно и дает вам обратный вызов, когда это делается. Вы хотите вызвать этот асинхронный метод синхронным образом.

Вы можете вызвать fun из обратного вызова. Что-то в этом роде (псевдокод):

int n;

callFunTenTimes()
{
    n = 0;
    fun(n);
}

callback()
{
    ++n;
    if (n < 10)
       fun(n);
    else
       print("done");
}

Это похоже на стиль передачи продолжения.

Преимущество этого метода заключается в том, что вы также можете сделать свой метод асинхронным без добавления каких-либо дополнительных потоков, блокировок или дополнительной логики — вы просто предоставляете функцию обратного вызова, на которую может подписаться ваш клиент. Он хорошо работает в среде, управляемой событиями.

person Mark Byers    schedule 06.02.2010
comment
Это забавно. Я думал о своей собственной проблеме в этом направлении: функция обратного вызова выполняет «мясо» того, что раньше делал цикл. Такое решение позволяет свести время ожидания к минимуму. - person Joe; 06.02.2010
comment
мне нужна асинхронная версия, потому что она обеспечивает обратный вызов прогресса, который является основным требованием. - person Madi D.; 06.02.2010
comment
@Madi D: Если требуется, чтобы загрузка выполнялась асинхронно, почему бы не сделать вашу функцию также асинхронной с обратным вызовом? Можете ли вы объяснить больше о том, для чего вы используете это? Это приложение WinForms, ASP.NET и т. д.? - person Mark Byers; 06.02.2010
comment
я отредактировал вопрос и добавил ответы на ваши вопросы, а также дополнительную информацию. - person Madi D.; 07.02.2010
comment
+1 за то, что научил меня новой концепции и дал мне альтернативное решение! - person Madi D.; 07.02.2010

Zebrabox правильно использует WaitHandle. Хотя решение Джульетты действительно работает, поток, выполняющий циклическое ожидание, потребляет значительный объем ресурсов процессора пропорционально дескриптору WaitHandle, который, по сути, будет бездействовать.

person Grant Back    schedule 06.02.2010
comment
Он не будет потреблять слишком много ресурсов процессора, но может помешать системе переводить процессор в более глубокое состояние простоя и, следовательно, быстрее разряжать батарею на ноутбуке. - person Ben Voigt; 08.02.2010

Проблема здесь:

for(int i=0;i < 10 ; i++)
{
  fun(); <-- if we block until this function finishes here, we stop the UI thread
}

То, что вы делаете, последовательно. И если вы не можете позволить себе заблокировать поток пользовательского интерфейса, переместите цикл из потока пользовательского интерфейса:

volatile downloadComplete;

void DownloadUpdates()
{
    ThreadPool.QueueUserWorkItem(state =>
        for(int i = 0; i < 10; i++)
        {
            downloadComplete = false;
            webClient.UploadFileAsync(uri, "PUT", fileNameOnHD);
            while(!downloadComplete) { Thread.Sleep(1); }
        });
}

Upload_Completed_callback()
{
    downloadComplete = true;
}

Теперь вы можете заблокировать выполнение цикла, не останавливая поток пользовательского интерфейса, а также воспользоваться преимуществами индикаторов выполнения из класса webclient.

person Juliet    schedule 06.02.2010
comment
Не используйте для этого логическую переменную, компилятор может оптимизировать ее (что может привести к тому, что поведение никогда не будет продолжаться, о чем упоминает OP). zebrabox показал правильный способ сделать это с ожидаемым объектом события. - person Ben Voigt; 06.02.2010
comment
@Ben: Я ценю ваши комментарии, но я думаю, что они вводят в заблуждение. Во-первых, компилятор не собирается оптимизировать логическое значение до тех пор, пока оно где-то используется или назначается (и, предположительно, так оно и есть). Во-вторых, ожидание вращения для логического значения и resetEvents являются допустимыми способами блокировки потока, оба являются правильными способами, ни один из них не является неправильным, выбор того или иного зависит от вкусов программиста. - person Juliet; 06.02.2010
comment
Большое спасибо за действительно полезный ответ, в итоге объединили потоки из вашего решения с решением @zebrabox resetEvent, и теперь оно работает.. =) - person Madi D.; 07.02.2010
comment
@Juliet, согласно правилам модели памяти .NET, эти два параметра абсолютно эквивалентны: while(!downloadComplete) { Thread.Sleep(1); } и если (!downloadComplete) { while (true) { Thread.Sleep(1); } } Поскольку цикл не изменяет переменную, компилятор может перенести проверку за пределы цикла. Я вижу, что теперь вы добавили синтаксическую ошибку, пытаясь сделать downloadComplete нестабильным. Если вы исправите объявление, это не позволит компилятору выполнить оптимизацию, о которой я говорил, но это все еще опрос, который хуже, чем событие по ряду причин. - person Ben Voigt; 08.02.2010
comment
@Ben: Я ценю ваш комментарий, но while(!downloadComplete) { Thread.Sleep(1); } и if (!downloadComplete) { while (true) { Thread.Sleep(1); } } не эквивалентны ни в одной модели памяти. Не могли бы вы предоставить некоторую документацию MSDN или пример кода, демонстрирующий, как опрос компилируется в предоставленный вами код (с volatile или без него)? - person Juliet; 09.02.2010
comment
@Juliet: Вот очень информативное обсуждение в контексте Java, но этот конкретный случай идентичен C# (поскольку оптимизация допустима при последовательной согласованности, а модели памяти Java и .NET CLR слабее): java.sun.com/docs/books/jls/ Third_edition/html/memory. html Обратите особое внимание на раздел 17.9 этой страницы. - person Ben Voigt; 11.02.2010