MST

星途 面试题库

面试题:C#中委托与事件处理机制 - 多线程场景

在多线程环境下使用C#的委托和事件处理机制可能会遇到哪些问题?如何解决这些问题?请结合代码示例说明。
32.4万 热度难度
编程语言C#

知识考点

AI 面试

面试题答案

一键面试

可能遇到的问题

  1. 线程安全问题:在多线程环境下,委托的调用列表可能会在调用过程中被其他线程修改,导致调用异常。例如,当一个线程正在遍历委托的调用列表并执行方法时,另一个线程移除了列表中的某个方法,就会引发 InvalidOperationException
  2. 跨线程访问UI控件问题:如果在非UI线程中引发与UI相关的事件,直接更新UI控件会导致跨线程操作异常,因为在Windows Forms和WPF中,只有创建UI控件的线程才能访问和更新该控件。

解决方法及代码示例

1. 解决委托调用列表修改的线程安全问题

using System;
using System.Threading;

public class ThreadSafeEventExample
{
    private EventHandler _eventHandler;

    // 线程安全的添加事件处理方法
    public void AddHandler(EventHandler handler)
    {
        lock (this)
        {
            _eventHandler += handler;
        }
    }

    // 线程安全的移除事件处理方法
    public void RemoveHandler(EventHandler handler)
    {
        lock (this)
        {
            _eventHandler -= handler;
        }
    }

    // 引发事件,线程安全
    public void RaiseEvent()
    {
        EventHandler localCopy;
        lock (this)
        {
            localCopy = _eventHandler;
        }
        localCopy?.Invoke(this, EventArgs.Empty);
    }
}

class Program
{
    static void Main()
    {
        var example = new ThreadSafeEventExample();
        example.AddHandler((sender, e) => Console.WriteLine("Event handled in thread {0}", Thread.CurrentThread.ManagedThreadId));

        Thread thread1 = new Thread(() => example.RaiseEvent());
        Thread thread2 = new Thread(() => example.RaiseEvent());

        thread1.Start();
        thread2.Start();

        thread1.Join();
        thread2.Join();
    }
}

在上述代码中,通过 lock 关键字来确保在添加、移除事件处理方法以及引发事件时,委托调用列表的一致性,避免了多线程环境下对调用列表的并发修改问题。

2. 解决跨线程访问UI控件问题(以Windows Forms为例)

using System;
using System.Windows.Forms;
using System.Threading;

public partial class Form1 : Form
{
    private event EventHandler _uiUpdateEvent;

    public Form1()
    {
        InitializeComponent();
        _uiUpdateEvent += UpdateUI;
    }

    private void UpdateUI(object sender, EventArgs e)
    {
        label1.Text = "UI updated in thread " + Thread.CurrentThread.ManagedThreadId;
    }

    private void button1_Click(object sender, EventArgs e)
    {
        Thread thread = new Thread(() =>
        {
            // 跨线程引发事件
            if (InvokeRequired)
            {
                Invoke(_uiUpdateEvent, this, EventArgs.Empty);
            }
            else
            {
                _uiUpdateEvent(this, EventArgs.Empty);
            }
        });
        thread.Start();
    }
}

在这个Windows Forms示例中,当按钮被点击时,启动一个新线程。在新线程中引发 _uiUpdateEvent 事件,通过 InvokeRequiredInvoke 方法来确保在UI线程上执行更新UI的操作,从而避免跨线程操作异常。如果是在WPF中,可以使用 Dispatcher 来达到类似的效果。