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

C# 在C中异步运行多个委托并等待响应#,c#,delegates,C#,Delegates,我有一个URL表,需要循环浏览,下载每个文件,更新表并返回结果。我希望一次最多下载10次,因此我考虑按如下方式使用代理: DataTable photos; bool scanning = false, complete = false; int rowCount = 0; public delegate int downloadFileDelegate(); public void page_load(){ photos = Database.getData...

我有一个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);

}
DataTable照片;
布尔扫描=假,
完整=错误;
int rowCount=0;
public delegate int downloadFileDelegate();
公共无效页面加载(){
照片=数据库。获取数据。。。
downloadFileDelegate d=下载文件;
d、 BeginInvoke(下载文件完成,d);
d、 BeginInvoke(下载文件完成,d);
d、 BeginInvoke(下载文件完成,d);
d、 BeginInvoke(下载文件完成,d);
d、 BeginInvoke(下载文件完成,d);
而(!complete){}
//处理结果。。。
}
int下载文件(){
while(scanning){}scanning=true;
数据行r;
对于(int ii=0;ii
但是,当我运行这个程序时,运行5所花费的时间与运行1所花费的时间相同。我原以为要快5倍


有什么想法吗?

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

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

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);
  • 您是否有理由不希望它们同时运行?这似乎是你练习的全部要点。我看不出这样做的原因,所以我会完全丢失
    扫描
    标志(以及相关逻辑)
  • 如果要采用这种方法,则需要将布尔标志声明为
    volatile
    (否则,它们的读取可能会被缓存,您可以无休止地等待)
  • 您的数据更新操作(更新
    DataRow
    中的值)必须在UI线程上进行。您必须将这些操作包装在
    控件中。Invoke
    控件。BeginInvoke
    调用,否则您将跨线程边界与控件交互
  • BeginInvoke
    返回一个
    AsyncWaitHandle
    。当操作全部完成时,将使用此逻辑。像这样的
  • -


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

    此实现中有许多东西对于并发使用是不安全的

    但可能导致您描述的效果的原因是
    downloadFile()
    有一个
    while()
    循环,用于检查
    扫描
    变量。此变量由正在运行的委托的所有实例共享。此while循环防止代理并发运行

    这个“扫描循环”不是一个正确的线程构造-两个线程可能同时读取变量和设置变量。您应该真正使用
    信号量
    lock()
    语句来保护数据表不受并发访问。由于每个线程大部分时间都在等待
    扫描
    为false,因此
    downloadFile
    方法不能同时运行


    您应该重新考虑如何构造此代码,以便在应用程序中下载数据和更新结构不会引起争议。

    我认为这样做会更好

    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();
        }
    }
    
    公共类光下载
    {
    公共手动重置事件完成{get;private set;}
    所需的公共对象数据{get;private set;}
    公共对象结果{get;private set;}
    }
    公开作废下载照片()
    {
    var photos=新列表();
    //创建照片下载列表
    foreach(照片中的var照片)
    {
    QueueUserWorkItem(下载照片,照片);
    }
    //等待下载完成
    foreach(照片中的var照片)
    {
    photo.Complete.WaitOne();
    }
    //确保一切都正确发生
    }
    公共void下载照片(对象状态)
    {
    var photo=状态为PhotoDownload;
    尝试
    {
    //不要访问此类中的字段
    //一切都应该在照片对象内
    }
    最后
    {
    photo.Complete.Set();
    }
    }
    
    这是一种更好的技术,但无助于提高性能。这并不能解决实际导致代码执行错误的问题,而不是对共享数据的不正确并发访问。在某些情况下,延迟最终会限制单个连接的吞吐量,多次下载可能会更快一些。我同意Yuliy的观点-我将“不会更快”改为“可能不会更快”,作为一个次要问题,你的代码不是线程安全的。是的,代码似乎试图序列化下载操作。我要指出,你指的是while(扫描)而不是while(!complete),正如最初我对你所指的感到困惑
    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();
        }
    }