8

I read a lot of event and threads discussion, but all of then focus in "what happen" if I unsuscribe from an event and try to call it later. My question is different...what will happen if I have a process in thread A that fires the event "I finish" in millisecond 1, and also have a process in thread B that fires the event "I finish" in millisecond 2.

Both processes are suscribed to the same method to listen and handle the event. So, C# has to execute the method that handles the event 2 times: 1 time for the event fired in thread A, and 1 time for the event fired from thread B.

What will happen?? Does C# locks the method when the "first event coming from thread A" starts execution of the method that handles the event, and unlock the method when it finish execution, thus allowing other waiting "events" to execute the method content??

Or the event fired from thread A will start execution of the method that handles the event, and 1 millisecond later the event that was fired from thread B will also start execution on the same method wihtout notice that currently the method is being executed by other "process"???

Im asking this, because I want to do some file writing in the method that catch the event, but if the method can be executed simultaneously (depending on when the event is fired), I guess I cannot do it here, since the info on the file will be a mix between 2 processes writing to the same file at the same time (not valid information on the file).

My code looks like this (a little bit long, sorry). please note this will not compile, is just a sample to show what Im doing:

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

3 回答 3

13

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

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

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

于 2013-04-25T13:48:41.443 回答
1

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

锁定语句

我喜欢微软给你提款的例子

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
            }
        }
    }
于 2013-04-25T13:48:54.550 回答
0

看来您已经使您的代码线程安全,因为您Form.Invoke在方法中使用client_FileDownloadCompleted

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

于 2013-04-25T14:02:33.757 回答