2013-04-25 31 views
7

我读了很多关于事件和线程的讨论,但是如果我从事件中删除并尝试稍后调用它,那么所有这些都将集中在“发生了什么”。 我的问题是不同的......如果我在线程A中有一个进程以毫秒1触发事件“我完成”,并且在线程B中有一个进程以毫秒2触发事件“我完成”将会发生什么? 。c#事件执行是线程安全的吗?

这两个进程都使用相同的方法来监听和处理事件。因此,C#必须执行处理事件2次的方法:1次在线程A中触发事件,1次从线程B触发事件。

会发生什么?当“来自线程A的第一个事件”开始执行处理该事件的方法时,C#是否锁定该方法,并在完成执行时解锁该方法,从而允许其他等待的“事件”执行方法内容?

或者从线程A触发的事件将开始执行处理该事件的方法,并且1毫秒后,从线程B触发的事件也将以相同的方法启动执行,而不会注意当前方法正在执行由其他“进程”执行?我想这样做,因为我想在捕捉事件的方法中做一些文件写入,但是如果方法可以同时执行(取决于事件何时被触发),我想我不能在这里做,因为文件上的信息是两个进程同时写入同一文件(文件中不是有效信息)的混合。

我的代码看起来像这样(有点长,对不起)。 请注意,这将无法编译,只是一个样本,以显示什么Im做:

public partial class MainForm : Form 
{ 
    FTPClientManager client = null; 

    public MainForm() 
    { 
     InitializeComponent(); 
    } 

    private void btnConnect_Click(object sender, EventArgs e) 
    { 
     Connect(this.tbFTPServer.Text.Trim()); 
     this.lstLog.Items.Add("Connected"); //This lstLog is a list box that will have a list of downloaded files from all threads. 
    } 

    void Connect(string urlStr) 
    { 
     try { 
      client = new FTPClientManager(); 
      //subscribe to the event 
      client.FileDownloadCompleted += new EventHandler<FileDownloadCompletedEventArgs>(client_FileDownloadCompleted); 
     } 
     catch (Exception ex) 
     { 
      MessageBox.Show(ex.Message); 
     } 
    } 

    void client_FileDownloadCompleted(object sender, FileDownloadCompletedEventArgs e) 
    { 
     this.Invoke(new EventHandler<FileDownloadCompletedEventArgs>(
      client_FileDownloadCompletedHandler), sender, e); 
    } 

    void client_FileDownloadCompletedHandler(object sender, FileDownloadCompletedEventArgs e) 
    { 
     string log = string.Format("{0} Instance {5} Download from {1} to {2} is completed. Length: {3} Time: {4}. ", 
      DateTime.Now, e.ServerPath, e.LocalFile.FullName, e.LocalFile.Length, e.DownloadTime, e.ftpInstance); 

     this.lstLog.Items.Add(log); 
    } 

    private void btnDownload_Click(object sender, EventArgs e) 
    { 
     client.DownloadFiles(); 
    } 
} 

public class FTPClientManager { 
    FTPDownloadClient[] arrayDownloadClient = new FTPDownloadClient[2];   
    public event EventHandler<FileDownloadCompletedEventArgs> FileDownloadCompleted; 

    public void DownloadFiles() 
    { 
     for (int i = 0; i < 2; i++) 
     { 
      arrayDownloadClient[i] = new FTPDownloadClient(); 
      //subscribe to the event. each instance of FTPDownloadClient will suscribe to the same event. 
      arrayDownloadClient[i].FileDownloadCompleted += new EventHandler<FileDownloadCompletedEventArgs>(downloadClient_FileDownloadCompleted); 
     } 

     //download one set of files in thread A 
     arrayDownloadClient[0].DownloadFiles(list_of_files_to_download); 

     //download another set of files in thread B 
     arrayDownloadClient[1].DownloadFiles(another_list_of_files_to_download); 
    } 

    //In theory, the method downloadClient_FileDownloadCompleted will be executed by any instance of FTPDownloadClient 
    //running in either thread A or thread B, whichever finish first downloading a file. 
    //My question comes in the execution of this method. 
    //Lets say the process in thread A finish downloading and fires the event. 
    //Lets say the process in thread B finish downloading 1 millisecond after thread A finish, so it also fires the event. 
    //how C# manage the execution of the downloadClient_FileDownloadCompleted?? 
    //does the event coming from thread A will lock the method downloadClient_FileDownloadCompleted, execute it, and when finish execution unlock the method 
    //and allows the event coming from thread B start locking, processing, unlock ?? 
    //Or the method will be executed "at the same time" (1 millisecond difference) by each event fired from thread A and thread B?? 
    void downloadClient_FileDownloadCompleted(object sender, FileDownloadCompletedEventArgs e) 
    { 
     this.OnFileDownloadCompleted(e); 
    } 

    protected virtual void OnFileDownloadCompleted(FileDownloadCompletedEventArgs e) 
    { 
     if (FileDownloadCompleted != null) 
     { 
      //this will fire the event, so the main form will catch it 
      //again, this fire can be triggered from process in thread A or from process in thread B 
      FileDownloadCompleted(this, e); 
     } 
    } 
} 

public class FTPDownloadClient { 
    public event EventHandler<FileDownloadCompletedEventArgs> 
      FileDownloadCompleted; 

    public void DownloadFiles(string [] files_to_download) 
    { 
     ParameterizedThreadStart threadStart = 
       new ParameterizedThreadStart(StartDownloadFiles); 
      Thread downloadThread = new Thread(threadStart); 
      downloadThread.IsBackground = true; 
      downloadThread.Start(new object[] { files_to_donwload }); 
    } 

    //This metod will download all the files in the list passed as parameter. 
    //Every file downloaded will raise the event FileDownloadComplete, so a message can be added to the lstlog on the main form 
    void StartDownloadFiles(object state) 
     { 
      var paras = state as object[]; 

      string [] files = paras[0] as string []; 

      foreach (var file in files) 
      { 
       DownloadOneFile(file); 
      } 
     } 

    void DownloadFile(string onefile) 
    { 
      //Donwload file done here 
      var fileDownloadCompletedEventArgs = new FileDownloadCompletedEventArgs 
      { 
       LocalFile = new FileInfo(destPath), 
       ServerPath = onefile, 
       DownloadTime = fileDownloadTime.ElapsedMilliseconds.ToString() 
      }; 

      this.OnFileDownloadCompleted(fileDownloadCompletedEventArgs); 
    } 

    protected virtual void OnFileDownloadCompleted(FileDownloadCompletedEventArgs e) 
     { 
      if (FileDownloadCompleted != null) 
      { 
       //the event is fired when the file being downloaded by this thread is finish. 
       //so, thread A will fire this event from its current thread 
       //and also thread B will fire the same event from its own thread. 
       FileDownloadCompleted(this, e); 
      } 
     } 
} 
+1

http://stackoverflow.com/questions/3480036/multiple-threads-subscribing-same-event – nicholas 2013-04-25 13:47:15

回答

12

C#不会做任何锁定你。如果事件可以同时由多个线程引发,则必须编写代码来处理该事件(如有必要)。

可以使用lock statement以防止多个线程执行它:

private void MyEventHandler(object sender, EventArgs e) 
{ 
    lock (lockingObject) 
    { 
     // Handle event here. 
     // Only one thread at a time can reach this code. 
    } 
} 

哪里lockingObject是你的类中的字段声明如下:

private readonly object lockingObject = new object(); 

你还必须要小心线程在引发事件的方法中。

假设您在班级中有一个名为MyEvent的活动。你应该做到这一点:

private void RaiseMyEvent() 
{ 
    if (MyEvent != null)     // {1} 
     MyEvent(this, new EventArgs()); // {2} 
} 

如果另一个线程可以从MyEvent分离,那么它可能是它可以在线{1}和线之间分离{2}。如果发生这种情况,第{2}行会抛出空引用异常,因为MyEvent会突然变为空!

正确的方式做,这就是:

private void RaiseMyEvent() 
{ 
    var handler = MyEvent; 

    if (handler != null) 
     handler (this, new EventArgs()); 
} 

现在空引用异常不可能发生。

但是,请注意,使用多个线程时,线程已将它分离后,可能会在之后调用事件处理程序!

+0

+1照顾的“空退订检查后,”竞态条件 – tobsen 2013-04-25 14:01:03

+2

新的C#6'?.'运算符还可以执行MyEvent?.Invoke(this,new EventArgs())'[MSDN](https://msdn.microsoft.com/en-us/library/dn986595.aspx#code-snippet-4) – SWdV 2016-02-21 13:15:59

1

看起来像你想使用锁。 它可以防止不同的线程在同一时间执行相同的代码。 但是,不建议使用锁定,应尽可能避免使用锁定,但在您的情况下它看起来不错。

Lock statement

我喜欢取钱

int Withdraw(int amount) 
    { 

     // This condition never is true unless the lock statement 
     // is commented out. 
     if (balance < 0) 
     { 
      throw new Exception("Negative Balance"); 
     } 

     // Comment out the next line to see the effect of leaving out 
     // the lock keyword. 
     lock (thisLock) 
     { 
      if (balance >= amount) 
      { 
       Console.WriteLine("Balance before Withdrawal : " + balance); 
       Console.WriteLine("Amount to Withdraw  : -" + amount); 
       balance = balance - amount; 
       Console.WriteLine("Balance after Withdrawal : " + balance); 
       return amount; 
      } 
      else 
      { 
       return 0; // transaction rejected 
      } 
     } 
    } 
+0

永远不要“锁定(this)”或锁定类型。相反,创建一个新的“syncObject”为[解释这里](http://msdn.microsoft.com/en-us/library/c5kehkcz(v = vs.80).aspx)(更新版本的msdn文章链接到)。 – tobsen 2013-04-25 13:57:20

+0

没有意识到它不是arcticle的最后一个版本。同意,更新它。永远不会有点苛刻,但我同意使用这种结构总是更好。 – 2013-04-25 14:06:17

0

看来,你已经做出你的代码线程安全的,因为你是在你的方法client_FileDownloadCompleted使用Form.Invoke的例子微软给你。

确实事件FTPClientManager.FileDownloadCompleted可能会在不同的线程上同时触发,但Form.Invoke会将每个回调序列化回您的主UI线程。因此,在您的代码中,您不需要任何锁定,因为Form.Invoke将会占用它,并且client_FileDownloadCompletedHandler将始终在您的UI线程中调用。

+0

感谢您的回答。但是,我将需要使用锁,因为我正在计划写入一个文件。 – user2232787 2013-04-25 14:13:06