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

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

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

假设你的类中有一个名为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()); 
}

现在,空引用异常不会发生。

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


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

锁定语句

我喜欢微软为您提供的退钱的例子

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

看来,因为你使用你已经作出了代码线程安全Form.Invoke在你的方法client_FileDownloadCompleted

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

链接地址: http://www.djcxy.com/p/68069.html

上一篇: c# events execution are thread safe?

下一篇: C# event handing in single threaded applications