2010-03-02 56 views
2

我有一张需要循环访问的url表,需要下载每个文件,更新表并返回结果。我想同时运行多达10次的下载,使正在考虑使用委托如下:异步运行多个委托并等待C#中的响应#

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倍的速度。

任何想法?

+1

作为一个问题,您的代码不是线程安全的。 – RichardOD 2010-03-02 18:48:04

回答

4

你看起来正在尝试使用无锁同步化(使用while(scanning)检查我们设置在布尔多线程是非常有用的函数的开始和最后的重置),但所有这些成功的做法是一次只运行一次检索。

  1. 是否有理由不让它们同时运行?这看起来就像你的练习的整个观点。我看不到这个原因,所以我完全失去了scanning标志(和相关的逻辑)。
  2. 如果你打算采取这种方法,你的布尔标志需要被声明为volatile(否则他们可能读取缓存,你可以无休止地等待)
  3. 你的数据更新操作(更新在DataRow值) 必须在UI线程上发生。您必须将这些操作包装在Control.InvokeControl.BeginInvoke调用中,否则您将跨越线程边界与控件进行交互。
  4. BeginInvoke返回AsyncWaitHandle。使用此逻辑将在操作完成时执行全部时发生。这样

东西 -

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

WaitHandle.WaitAll(handles); 

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

+0

是的,它似乎代码试图序列化下载操作。 – 2010-03-02 18:49:27

+0

我指出你指的是while(扫描),而不是while(!complete),因为最初我对你指的是哪一方面感到困惑。 – RichardOD 2010-03-02 18:55:09

+0

为了使代码被写入同时运行,需要保护对DataTable的访问。或者,异步逻辑需要从共享数据结构的更新中分解出来。 – LBushkin 2010-03-02 18:56:21

0

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

+2

在某些情况下,延迟最终会限制单个连接的吞吐量,并且多次下载最终可能会更快。 – Yuliy 2010-03-02 18:48:02

+0

我同意Yuliy-我会将“不会更快”改写为“可能不会更快” – RichardOD 2010-03-02 18:49:03

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

这是一种更好的技术,但无法提高性能。 – RichardOD 2010-03-02 18:47:46

+0

这没有解决实际导致代码错误执行的问题,而不是对共享数据的不当并发访问。 – LBushkin 2010-03-02 18:51:46

0

在这个实现中有许多事情并不安全的并发使用。

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

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

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

+0

在这种情况下,似乎“扫描循环”是完全不必要的,除非省略了某些内容。 – 2010-03-02 18:59:05

+0

@Adam Robinson:扫描循环的目的似乎是保护DataTable免受并发访问。但我可能是错的 - 代码的结构使得很难推断意图。 – LBushkin 2010-03-02 19:06:24

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