c#事件执行是线程安全的吗?

我阅读了很多事件和线程讨论,但是如果我从事件中取消并稍后尝试调用它,那么所有这些都会集中在“会发生什么”。 我的问题是不同的……如果我在线程A中有一个进程以毫秒1触发事件“我完成”,并且在线程B中有一个进程以毫秒2触发事件“我完成”将会发生什么。

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

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

或者从线程A触发的事件将开始执行处理事件的方法,1毫秒后,从线程B触发的事件也将在相同的方法上开始执行,而不会注意到当前该方法正由其他方执行“处理”???

我问这个,因为我想在捕获事件的方法中写一些文件,但如果方法可以同时执行(取决于事件被触发的时间),我想我不能在这里做,因为关于该文件将是同时写入同一文件的2个进程之间的混合(不是文件的有效信息)。

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

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(client_FileDownloadCompleted); } catch (Exception ex) { MessageBox.Show(ex.Message); } } void client_FileDownloadCompleted(object sender, FileDownloadCompletedEventArgs e) { this.Invoke(new EventHandler( 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 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(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 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); } } } 

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

您可以使用lock语句来阻止多个线程执行它:

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

您还必须小心在引发事件的方法中进行线程化。

假设您的class级中有一个名为MyEvent的事件。 你不应该这样做:

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

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

正确的方法是:

 private void RaiseMyEvent() { var handler = MyEvent; if (handler != null) handler (this, new EventArgs()); } 

现在不能发生null引用exception。

但是,请注意,当使用多个线程时,可以在线程分离之后调用事件处理程序!

看起来你想要使用锁。 它可以防止不同的线程同时执行相同的代码。 但是建议不要使用锁定,并且应该在大多数情况下避免使用锁定,但在您的情况下看起来没问题。

锁定声明

我喜欢微软给你取钱的例子

 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 } } } 

您似乎已经使代码线程安全,因为您在方法client_FileDownloadCompleted中使用client_FileDownloadCompleted

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