MST

星途 面试题库

面试题:C# 委托与事件在多线程环境下的应用及问题

在多线程编程场景中,使用 C# 的委托和事件可能会遇到哪些问题?如何有效地解决这些问题?请结合具体代码示例,阐述线程安全的委托与事件处理的实现方法。
21.8万 热度难度
编程语言C#

知识考点

AI 面试

面试题答案

一键面试

可能遇到的问题

  1. 线程安全问题:当多个线程同时访问和调用委托时,可能会出现竞态条件。例如,多个线程同时向委托添加或移除方法,可能导致委托状态不一致。
  2. 跨线程访问异常:在 Windows 窗体或 WPF 应用程序中,如果从非 UI 线程调用与 UI 相关的委托,会引发跨线程访问异常。

解决方法

  1. 线程安全问题解决
    • 使用 lock 关键字来确保在同一时间只有一个线程可以访问委托的添加、移除操作。
    • 使用 Interlocked 类中的方法(如 Interlocked.CompareExchange)来实现线程安全的委托操作。
  2. 跨线程访问异常解决
    • 在 Windows 窗体应用程序中,使用 Control.InvokeControl.BeginInvoke 方法。
    • 在 WPF 应用程序中,使用 Dispatcher.InvokeDispatcher.BeginInvoke 方法。

线程安全的委托与事件处理的实现示例

  1. 使用 lock 关键字
using System;
using System.Threading;

class ThreadSafeDelegateExample
{
    private static readonly object _lockObject = new object();
    private static Action _myDelegate;

    public static event Action MyEvent
    {
        add
        {
            lock (_lockObject)
            {
                _myDelegate += value;
            }
        }
        remove
        {
            lock (_lockObject)
            {
                _myDelegate -= value;
            }
        }
    }

    public static void FireEvent()
    {
        Action localCopy;
        lock (_lockObject)
        {
            localCopy = _myDelegate;
        }
        localCopy?.Invoke();
    }
}

class Program
{
    static void Main()
    {
        ThreadSafeDelegateExample.MyEvent += () => Console.WriteLine("Event handler 1");
        ThreadSafeDelegateExample.MyEvent += () => Console.WriteLine("Event handler 2");

        Thread thread1 = new Thread(() => ThreadSafeDelegateExample.FireEvent());
        Thread thread2 = new Thread(() => ThreadSafeDelegateExample.FireEvent());

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

        thread1.Join();
        thread2.Join();
    }
}
  1. Windows 窗体跨线程访问示例
using System;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private delegate void UpdateLabelDelegate(string text);

        private void UpdateLabel(string text)
        {
            if (this.InvokeRequired)
            {
                this.Invoke(new UpdateLabelDelegate(UpdateLabel), text);
            }
            else
            {
                label1.Text = text;
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Thread thread = new Thread(() =>
            {
                for (int i = 0; i < 10; i++)
                {
                    UpdateLabel($"Count: {i}");
                    Thread.Sleep(1000);
                }
            });
            thread.Start();
        }
    }
}
  1. WPF 跨线程访问示例
using System;
using System.Threading;
using System.Windows;

namespace WpfApp1
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void UpdateTextBlock(string text)
        {
            if (Dispatcher.CheckAccess())
            {
                textBlock1.Text = text;
            }
            else
            {
                Dispatcher.Invoke(() => UpdateTextBlock(text));
            }
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            Thread thread = new Thread(() =>
            {
                for (int i = 0; i < 10; i++)
                {
                    UpdateTextBlock($"Count: {i}");
                    Thread.Sleep(1000);
                }
            });
            thread.Start();
        }
    }
}