异步方法中Brush对象引发InvalidOperationException的解决方案咨询
嘿,这个问题我太熟了!GDI+的那些绘图对象(比如Brush、Pen)天生就不是线程安全的,而且它们还和创建时的线程绑定,跨线程碰就容易抛出那种“正被其他线程占用”的异常,异步场景下尤其容易踩这个坑。给你几个实用的解决方案:
每个异步任务/线程创建独立的Brush实例
别想着复用同一个Brush对象了,每次需要用的时候,就在当前执行的线程(也就是异步任务的上下文里)新建一个,用完立刻释放。用using块是最省心的方式,它会自动帮你调用Dispose(),既避免资源泄漏,又保证每个任务用的是自己的Brush:await Task.Run(() => { using (var solidBrush = new SolidBrush(Color.DarkGreen)) { // 在这里执行你的配置保存或绘图操作 } });绝对不要跨异步任务共享GDI+对象
如果你之前有全局的Brush实例(比如一个静态字段),赶紧把这个设计改掉。哪怕你加锁同步访问也不推荐——一来锁会把异步操作变成同步,完全失去异步的意义;二来GDI+对象的线程绑定特性,就算加了锁,跨线程访问还是可能出各种奇怪的问题。高成本Brush用ThreadLocal
做线程本地存储
如果你的Brush创建成本很高(比如复杂的渐变Brush、纹理Brush),不想每次都新建,可以用ThreadLocal<T>给每个线程分配一个专属实例。这样每个线程用自己的Brush,不会互相干扰:// 全局初始化线程本地Brush private static readonly ThreadLocal<LinearGradientBrush> _threadLocalGradientBrush = new ThreadLocal<LinearGradientBrush>(() => new LinearGradientBrush( new Point(0, 0), new Point(100, 100), Color.Red, Color.Yellow ) ); // 在异步方法里使用 await Task.Run(() => { var brush = _threadLocalGradientBrush.Value; // 执行你的操作 });注意:记得在应用关闭或者不再需要这些Brush的时候,遍历
_threadLocalGradientBrush.Values,手动调用Dispose()释放所有实例,不然会有内存泄漏。留意异步操作中的线程切换
有时候await会导致线程切换——比如你在UI线程创建了Brush,await之后回到了后台线程,再用这个Brush就会炸。这种情况要确保Brush是在await之后的线程创建的,或者用ConfigureAwait(false)避免回到原上下文(如果不需要的话):public async Task SaveAppSettingsAsync() { // 先执行一些异步操作,此时可能切换线程 await FetchSomeDataAsync().ConfigureAwait(false); // 现在在后台线程,创建当前线程专属的Brush using (var brush = new SolidBrush(Color.Purple)) { // 执行配置保存相关的操作 } }
总的来说,核心原则就是GDI+绘图对象属于创建它的线程,绝对不能跨线程共享。按照上面的方法调整,应该就能解决那个InvalidOperationException了。
内容的提问来源于stack exchange,提问作者simmeone

