Запуск нескольких делегатов асинхронно и ожидание ответа в C # - PullRequest
2 голосов
/ 02 марта 2010

У меня есть таблица URL-адресов, которые мне нужно просмотреть, загрузить каждый файл, обновить таблицу и вернуть результаты. Я хочу запустить до 10 загрузок за раз, поэтому думаю об использовании делегатов следующим образом:

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);

}

Однако когда я запускаю это, для запуска 5 требуется столько же времени, сколько и для 1. Я ожидал, что это займет в 5 раз быстрее.

Есть идеи?

Ответы [ 5 ]

4 голосов
/ 02 марта 2010

Вы выглядите так, как будто пытаетесь использовать синхронизацию без блокировки (используя while(scanning) для проверки логического значения, установленного в начале функции и сбрасываемого в конце), но все, что вам удастся сделать, это запустить только один поиск в время.

  1. Есть ли причина, по которой вы не хотите, чтобы они запускались одновременно? Это похоже на весь смысл вашего упражнения. Я не вижу причины для этого, поэтому я полностью потеряю флаг scanning (и связанную логику).
  2. Если вы собираетесь использовать этот подход, ваши логические флаги должны быть объявлены как volatile (в противном случае их чтения могут быть кэшированы, и вы можете ждать бесконечно)
  3. Ваши операции обновления данных (обновление значения в DataRow) должны выполняться в потоке пользовательского интерфейса. Вам придется заключить эти операции в вызов Control.Invoke или Control.BeginInvoke, иначе вы будете взаимодействовать с элементом управления через границы потоков.
  4. BeginInvoke возвращает AsyncWaitHandle. Используйте это для своей логики, которая будет иметь место, когда все операции all завершены. Как то так

-

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

 WaitHandle.WaitAll(handles);

Это приведет к блокировке вызывающего потока, пока все операции не будут завершены.

0 голосов
/ 02 марта 2010

Мне кажется, что-то подобное будет работать лучше.

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();
    }
}
0 голосов
/ 02 марта 2010

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

Но то, что может вызвать эффект, который вы описываете, это тот факт, что downloadFile() имеет цикл while(), который проверяет переменную scanning. Эта переменная является общей для всех экземпляров работающего делегата. Этот цикл while запрещает одновременный запуск делегатов.

Этот "цикл сканирования" не является правильной структурой потоков - возможно, что два потока одновременно прочитают переменную и оба установят ее одновременно . Вы действительно должны использовать оператор Semaphore или lock() для защиты DataTable от одновременного доступа. Поскольку каждый поток тратит большую часть своего времени в ожидании ложного scanning, метод downloadFile не может выполняться одновременно.

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

0 голосов
/ 02 марта 2010
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);
0 голосов
/ 02 марта 2010

Это займет столько же времени, если вы ограничены пропускной способностью сети. Если вы загружаете все 10 файлов с одного и того же сайта, то это будет не так быстро. Многопоточность полезна, когда вы хотите, чтобы пользовательский интерфейс реагировал или у вас было что-то интенсивное использование процессора и несколько ядер

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...