Android多线程:如何自定义消费BlockingQueue中的Runnables?
嘿,刚好在Xamarin.Android里做过类似的多线程计算任务,给你拆解下这几个需求的实现思路,都是实战里验证过的:
1. 拆分无符号整数遍历任务到多线程
unsigned int的取值范围是0到4294967295,要把这个超大区间拆给多线程处理,核心是切分均等的子区间,让每个线程负责一段的遍历和计算,避免单线程卡死UI。
比如可以根据CPU核心数来分配线程(充分利用硬件资源),代码示例:
uint totalRange = uint.MaxValue; int threadCount = Environment.ProcessorCount; // 获取当前设备CPU核心数 uint chunkSize = totalRange / (uint)threadCount; // 给每个线程分配专属区间 for (int i = 0; i < threadCount; i++) { uint start = (uint)i * chunkSize; // 最后一个线程要兜底处理剩余的所有值,避免遗漏边界 uint end = i == threadCount - 1 ? uint.MaxValue : start + chunkSize - 1; // 启动后台线程执行任务 Task.Run(() => ProcessRange(start, end)); } // 你的核心计算逻辑 private void ProcessRange(uint start, uint end) { for (uint i = start; i <= end; i++) { // 这里写你的遍历计算代码,比如判断i是否符合某个条件、执行运算等 DoComplexCalculation(i); } }
如果多个线程需要共享计算结果,一定要用线程安全的集合(比如ConcurrentBag<T>)或者lock语句加锁,避免数据竞争导致结果错误。
2. 线程更新进度条及视图
Android的UI控件只能在主线程更新,子线程直接操作会抛出异常,Xamarin里有两种常用的UI更新方式:
方式一:用RunOnUiThread切换上下文
在子线程中完成部分计算后,调用Activity的RunOnUiThread方法切回主线程更新UI:
// 定义线程安全的计数器,累计已处理的数量 private long processedCount = 0; private void ProcessRange(uint start, uint end) { for (uint i = start; i <= end; i++) { DoComplexCalculation(i); // 用Interlocked保证多线程下计数的原子性 long current = Interlocked.Increment(ref processedCount); // 计算进度百分比 int progress = (int)((current / (double)uint.MaxValue) * 100); // 切回UI线程更新进度条和文本 RunOnUiThread(() => { progressBar.Progress = progress; tvProgress.Text = $"已完成:{progress}%"; }); } }
方式二:用Async/Await简化上下文切换
如果你的计算逻辑可以异步化,await会自动帮你切回UI线程,代码更简洁:
private async Task StartTotalCalculation() { uint totalRange = uint.MaxValue; int threadCount = Environment.ProcessorCount; uint chunkSize = totalRange / (uint)threadCount; var taskList = new List<Task>(); for (int i = 0; i < threadCount; i++) { uint start = (uint)i * chunkSize; uint end = i == threadCount - 1 ? uint.MaxValue : start + chunkSize - 1; taskList.Add(ProcessRangeAsync(start, end)); } await Task.WhenAll(taskList); Toast.MakeText(this, "全部计算完成!", ToastLength.Short).Show(); } private async Task ProcessRangeAsync(uint start, uint end) { for (uint i = start; i <= end; i++) { DoComplexCalculation(i); long current = Interlocked.Increment(ref processedCount); int progress = (int)((current / (double)uint.MaxValue) * 100); // 空Delay触发上下文切换,await后自动回到UI线程 await Task.Delay(0); progressBar.Progress = progress; tvProgress.Text = $"已完成:{progress}%"; } }
3. 自定义消费BlockingQueue中的Runnables
Xamarin没有原生的BlockingQueue,但可以用ConcurrentQueue<T>配合SemaphoreSlim实现一个阻塞队列,再自定义消费者线程处理队列中的任务:
第一步:实现阻塞队列
public class BlockingQueue<T> { private readonly ConcurrentQueue<T> _innerQueue = new ConcurrentQueue<T>(); private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(0); // 入队:添加任务后释放信号量通知消费者 public void Enqueue(T item) { _innerQueue.Enqueue(item); _semaphore.Release(); } // 出队:队列为空时阻塞,直到有新任务 public async Task<T> DequeueAsync() { await _semaphore.WaitAsync(); _innerQueue.TryDequeue(out T item); return item; } }
第二步:启动消费者线程处理任务
假设你的任务是Action类型的可执行逻辑,代码示例:
private BlockingQueue<Action> _taskQueue = new BlockingQueue<Action>(); // 启动消费者线程 private void StartTaskConsumer() { Task.Run(async () => { while (true) { // 阻塞等待队列中的任务 Action task = await _taskQueue.DequeueAsync(); try { // 执行任务 task.Invoke(); // 如果任务需要更新UI,记得切回主线程 RunOnUiThread(() => { // 这里写视图更新逻辑,比如任务完成提示 }); } catch (Exception ex) { // 处理任务执行异常 RunOnUiThread(() => { Toast.MakeText(this, $"任务执行失败:{ex.Message}", ToastLength.Short).Show(); }); } } }); } // 往队列中添加任务 private void AddTask(Action task) { _taskQueue.Enqueue(task); }
你可以把遍历计算的小任务一个个加到队列里,消费者线程会自动取出执行,还能通过信号量控制并发数(比如限制同时执行3个任务),灵活性很高。
内容的提问来源于stack exchange,提问作者aslg
相关产品推荐
相关产品推荐

