异步运行多个委托并在C#中等待响应

时间:2010-03-02 18:40:46

标签: c# delegates

我有一个urls表,我需要循环,下载每个文件,更新表并返回结果。我想一次最多运行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,而我预计它需要花费5倍的速度。

有什么想法吗?

5 个答案:

答案 0 :(得分:4)

你看起来好像在尝试使用无锁同步(使用while(scanning)来检查在函数开始时设置的布尔值,并在结束时重置),但所有这些成功的做法只是运行一个一次检索。

  1. 您是否有理由不希望它们同时运行?这似乎是你锻炼的全部要点。我看不出这个原因,所以我完全失去了scanning标志(及相关逻辑)。
  2. 如果你要采用这种方法,你的布尔标志需要声明为volatile(否则它们的读取可以被缓存,你可以无休止地等待)
  3. 您的数据更新操作(更新DataRow中的值必须在UI线程上进行。您必须在Control.InvokeControl.BeginInvoke调用中包装这些操作,否则您将跨越线程边界与控件进行交互。
  4. BeginInvoke会返回AsyncWaitHandle。将此用于您的操作全部完成时将要发生的逻辑。像这样的东西
  5. -

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

    这将导致调用线程阻塞,直到所有操作完成。

答案 1 :(得分:0)

如果受网络带宽限制,则需要相同的时间。如果您从同一站点下载所有10个文件,那么它将不会更快。当您希望用户界面响应或者您拥有处理器密集型和多核心

时,多线程非常有用

答案 2 :(得分:0)

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

答案 3 :(得分:0)

此实现中有许多内容对于并发使用是不安全的。

但可能导致您描述的效果的是downloadFile()有一个while()循环来检查scanning变量。此变量由正在运行的委托的所有实例共享。 while while循环阻止代理同时运行。

这个“扫描循环”不是一个合适的线程构造 - 两个线程都可以同时读取变量并同时将它设置为 。您应该使用Semaphorelock()语句来保护DataTable免受并发访问。由于每个线程花费大部分时间等待scanning为假,因此downloadFile方法不能同时运行。

您应该重新考虑如何构建此代码,以便下载数据和更新应用程序中的结构不会出现争用。

答案 4 :(得分:0)

我认为这样的事情会更好。

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