C#中Dispatcher.Invoke 和 Dispatcher.BeginInvoke 的区别及队列的使用

Dispatcher

Dispatcher.InvokeDispatcher.BeginInvoke 都是 WPF(Windows Presentation Foundation)中用于在 UI 线程上执行操作的方法。

它们的主要区别在于调用的方式和线程控制。

下面是它们的详细区别以及使用示例:

Dispatcher.Invoke

  • 同步调用Dispatcher.Invoke 会阻塞当前线程,直到指定的操作完成。这意味着调用线程会等待操作完成后再继续执行。
  • 适用场景:当你需要在 UI 线程上执行某些操作并且希望在操作完成之前当前线程继续等待时使用。

示例

1
2
3
4
5
6
// 假设我们在非 UI 线程上
Dispatcher.Invoke(() =>
{
// 更新 UI 元素的代码
myTextBox.Text = "Hello, World!";
});

在这个例子中,Dispatcher.Invoke 会阻塞当前线程,直到 myTextBox.Text 更新操作完成为止。

Dispatcher.BeginInvoke

  • 异步调用Dispatcher.BeginInvoke 会立即返回,不会阻塞当前线程。指定的操作将在 UI 线程上异步执行。
  • 适用场景:当你需要在 UI 线程上执行某些操作但不希望当前线程等待操作完成时使用。适合需要提高应用程序响应性的情况。

示例

1
2
3
4
5
6
// 假设我们在非 UI 线程上
Dispatcher.BeginInvoke(new Action(() =>
{
// 更新 UI 元素的代码
myTextBox.Text = "Hello, World!";
}));

在这个例子中,Dispatcher.BeginInvoke 会立即返回,而 myTextBox.Text 的更新操作会在 UI 线程上异步进行。

总结

  • Dispatcher.Invoke:用于同步执行,调用线程会等待直到操作完成。
  • Dispatcher.BeginInvoke:用于异步执行,调用线程不会等待,操作将在 UI 线程上异步进行。

根据具体的需求选择合适的方法。如果你需要保证操作完成后才能继续执行其他任务,使用 Invoke;如果你希望操作在后台线程上执行,而当前线程继续进行其他工作,使用 BeginInvoke

队列

Queue

以下是Queue集合的基本用法:

创建队列:

1
Queue<string> queue = new Queue<string>();

添加元素:

1
queue.Enqueue("第一个元素");

移除并返回第一个元素:

1
2
string element = queue.Dequeue();
Console.WriteLine(element);

查看但不移除第一个元素:

1
2
string first = queue.Peek();
Console.WriteLine("第一个元素是:" + first);

检查队列是否为空:

1
bool isEmpty = queue.Count == 0;

获取队列中的元素数量:

1
int count = queue.Count;

将集合的元素全部加入队列:

1
2
3
4
5
queue.Clear(); // 清空队列
queue.TrimToSize(); // 根据当前元素数量调整容量

List<string> list = new List<string> { "A", "B", "C" };
queue.EnqueueRange(list);

转换为数组:

1
string[] array = queue.ToArray();

清空队列:

1
queue.Clear();

请注意,Queue类是非线程安全的,如果你的应用程序需要处理多线程环境,你可能需要使用ConcurrentQueue或考虑使用Queue<T>并同步对队列的访问。

下面是Queue<T>的用法示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Queue<int> queueGeneric = new Queue<int>();

queueGeneric.Enqueue(1);
queueGeneric.Enqueue(2);
queueGeneric.Enqueue(3);

int elementGeneric = queueGeneric.Dequeue();
Console.WriteLine(elementGeneric);

int firstElementGeneric = queueGeneric.Peek();
Console.WriteLine("第一个元素是:" + firstElementGeneric);

bool isEmptyGeneric = queueGeneric.Count == 0;

int countGeneric = queueGeneric.Count;

使用Queue<T>避免了装箱和拆箱的开销,提供了类型安全,因此推荐使用Queue<T>而不是非泛型的Queue

ConcurrentQueue

ConcurrentQueue<T> 是 .NET 提供的一个线程安全的队列实现,它位于 System.Collections.Concurrent 命名空间中。

它允许多个线程同时进行操作,而不会引发数据竞争问题。ConcurrentQueue<T> 实现了先进先出(FIFO)的数据结构,适合在多线程环境下使用。

以下是 ConcurrentQueue<T> 的基本用法示例:

创建队列:

1
ConcurrentQueue<int> queue = new ConcurrentQueue<int>();

添加元素:

Enqueue 方法用于将元素添加到队列的末尾。

1
2
3
queue.Enqueue(1);
queue.Enqueue(2);
queue.Enqueue(3);

移除并返回第一个元素:

TryDequeue 方法尝试从队列的开头移除并返回一个元素。这个方法是线程安全的,能够避免由于线程竞争导致的问题。

1
2
3
4
5
6
7
8
if (queue.TryDequeue(out int result))
{
Console.WriteLine("Dequeued element: " + result);
}
else
{
Console.WriteLine("Queue was empty.");
}

查看但不移除第一个元素:

TryPeek 方法尝试查看队列的第一个元素,但不会将其移除。这也是线程安全的。

1
2
3
4
5
6
7
8
if (queue.TryPeek(out int peekResult))
{
Console.WriteLine("Peeked element: " + peekResult);
}
else
{
Console.WriteLine("Queue was empty.");
}

检查队列是否为空:

可以使用 Count 属性来获取队列中的元素数量,尽管这个属性的值可能在访问过程中发生变化。

1
2
int count = queue.Count;
Console.WriteLine("Queue count: " + count);

清空队列:

ConcurrentQueue<T> 本身没有直接的清空方法,但是可以通过不断调用 TryDequeue 方法来移除所有元素。

1
2
3
4
while (queue.TryDequeue(out _))
{
// Do something if needed
}

转换为数组:

可以使用 ToArray 方法将队列转换为数组,但这会产生一个快照,并不会影响队列中的实际元素。

1
int[] array = queue.ToArray();

并发示例:

以下是一个简单的多线程示例,演示了如何在多个线程中使用 ConcurrentQueue<T>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
ConcurrentQueue<int> queue = new ConcurrentQueue<int>();

// 生产者线程
Task producer = Task.Run(() =>
{
for (int i = 0; i < 10; i++)
{
queue.Enqueue(i);
Console.WriteLine($"Produced: {i}");
Thread.Sleep(100); // 模拟一些工作
}
});

// 消费者线程
Task consumer = Task.Run(() =>
{
while (true)
{
if (queue.TryDequeue(out int item))
{
Console.WriteLine($"Consumed: {item}");
}
else
{
Thread.Sleep(50); // 等待新元素
}
}
});

// 等待任务完成
Task.WaitAll(producer, consumer);

以上代码演示了如何在多个线程中安全地添加和移除队列元素。

ConcurrentQueue<T> 设计用于处理多线程环境中的数据访问,因此是处理并发任务时的一个良好选择。

场景示例

假如数据的产出速度大于渲染消费的速度,这样需要渲染的数据就会越累越多,我们可以用队列处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private readonly Queue<byte[]> _imgQueue = new Queue<byte[]>();

public void ReceiveImage(byte[] bArr)
{
_imgQueue.Enqueue(bArr);
while (_imgQueue.Count > 1)
{
_imgQueue.Dequeue();
}
Dispatcher.BeginInvoke(
new Action(
() =>
{
if (_imgQueue.Count > 0)
{
byte[] imgByteArr = _imgQueue.Dequeue();
}
}
)
);
}