前言
在开发过程中,线程问题一直是困扰许多开发者的难题。尤其在群里,经常看到小伙伴们询问关于线程切换、UI线程操作等相关问题。尽管市面上有很多视频教程,但不少人反映难以从中找到清晰、直接的解决方案。因此,决定结合自己的实践经验,写一篇关于UI线程切换的总结,帮助大家更好地理解和掌握这一关键技术。
UI线程切换的核心思路
1、直接修改UI的代码必须放在UI线程
这是UI线程操作的基本原则。在WinForms中,你可以通过将Control类的静态属性CheckForIllegalCrossThreadCalls设为false来关闭线程检查,但前提是你必须清楚自己在做什么,否则可能导致不可预知的错误。
2、WPF中的线程检查
与WinForms不同,WPF无法关闭线程检查。但WPF的ViewModel不需要UI线程进行更新,因为它通过内部通知机制来实现数据绑定和更新,这使得WPF在处理UI线程时更加灵活。
开启一个新的任务
在不同的.NET版本中,开启新任务的方式有所不同。以下是几种常见的方法:
Net4.5及以后版本
var param = 123;
Task.Run(() =>
{
DoSomthing(param);
});
Task.Run(async () =>
{
await DoSomthingAsync(param);
});
Net 4.0
Task.Factory.StartNew(delegate ()
{
DoSomthing(param);
});
Net 3.5
Thread t = new Thread((ThreadStart)delegate ()
{
DoSomthing(param);
});
t.Start();
// 使用线程池
ThreadPool.QueueUserWorkItem((WaitCallback)delegate (Object obj)
{
DoSomthing(param);
});
为了简化说明,后续示例将主要使用Task。
回到UI线程
在完成后台任务后,经常需要将结果返回到UI线程进行更新。以下是WinForm和WPF中回到UI线程的方法:
WinForm
Task.Run(() =>
{
// 类似SendMessage,等待发送结束
this.Invoke((Action)delegate ()
{
DoSomthing(param);
});
// 类似PostMessage,发了就跑
this.BeginInvoke((Action)delegate ()
{
DoSomthing(param);
});
});
WPF
Task.Run(async () =>
{
this.Dispatcher.Invoke(delegate ()
{
DoSomthing(param);
});
// 使用异步等待任务结束
await this.Dispatcher.BeginInvoke((Action)delegate ()
{
DoSomthing(param);
});
// 使用抛弃返回值的方式,直接过,不等待
_ = this.Dispatcher.BeginInvoke((Action)delegate ()
{
DoSomthing(param);
});
});
高级方法
使用SynchronizationContext
// 在UI线程时记录下上下文
var syncContext = SynchronizationContext.Current;
Task.Run(() =>
{
// 使用UI线程
syncContext.Post(o =>
{
DoSomthing(param);
}, null);
});
await一个SynchronizationContext
// 在UI线程时记录下上下文
var syncContext = SynchronizationContext.Current;
Task.Run(() =>
{
// 回到UI线程
await syncContext;
DoSomthing(param); // 在UI线程中执行
});
如何await一个SynchronizationContext
// 写个Awaiter类
public sealed class SynchronizationContextAwaiter : INotifyCompletion
{
private readonly SynchronizationContext _context;
public SynchronizationContextAwaiter(SynchronizationContext context) =>
_context = context ?? throw new ArgumentNullException("context");
public bool IsCompleted => SynchronizationContext.Current == _context;
public void OnCompleted(Action action) => _context.Post(x => action(), null);
public void GetResult() { }
}
// 写个扩展方法
public static SynchronizationContextAwaiter GetAwaiter(this SynchronizationContext context)
{
return new SynchronizationContextAwaiter(context);
}
将高级方法整合到一个类中
为了更好地管理和使用UI线程切换,我们可以将上述高级方法整合到一个类中。
以下是一个完整的示例:
初始化
// 需要在UI线程初始化
UIThreadContext.Init();
UIThreadContext类
using Ruifei.Common.UIThread;
using System;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
public static class UIThreadContext
{
public static SynchronizationContext Context;
public static Thread UIThread => uiThread;
static Thread uiThread = null;
public static void SendUIThread(Action action)
{
if (Context != null)
Context.Send(x => action(), null);
else
action();
}
public static void SendUIThreadIfRequired(Action action)
{
Thread crt = Thread.CurrentThread;
if (uiThread != crt)
SendUIThread(action);
else
action();
}
public static void PostUIThread(Action action)
{
if (Context != null)
Context.Post(x => action(), null);
else
action();
}
public static void PostUIThread(Func<Task> action)
{
if (Context != null)
Context.Post(x => action(), null);
else
action();
}
public static void PostUIThreadIfRequired(Action action)
{
if (IsInUIThread())
action();
else
PostUIThread(action);
}
public static void PostUIThreadIfRequired(Func<Task> action)
{
if (IsInUIThread())
action();
else
PostUIThread(action);
}
public static bool IsInUIThread()
{
return uiThread == Thread.CurrentThread;
}
static Dispatcher dispatcher = (Application.Current != null && Application.Current.Dispatcher != null) ? Application.Current.Dispatcher : Dispatcher.CurrentDispatcher;
public async static void InvokeOnUIThread(Func<Task> action)
{
if (dispatcher == null || dispatcher.CheckAccess())
await action();
else
await dispatcher.BeginInvoke(action);
}
public static void InvokeOnUIThread(Action action)
{
if (dispatcher == null || dispatcher.CheckAccess())
action();
else
dispatcher.BeginInvoke(action);
}
public static void Init()
{
Context = SynchronizationContext.Current;
uiThread = Thread.CurrentThread;
}
public static SynchronizationContextAwaiter GetAwaiter(this SynchronizationContext context)
{
return new SynchronizationContextAwaiter(context);
}
public static SynchronizationContext SwitchToUIThread()
{
return UIThreadContext.Context;
}
public static WaitForSwitchToNewTask SwitchToNewTask()
{
return new WaitForSwitchToNewTask(false);
}
}
namespace Ruifei.Common.UIThread
{
public class WaitForSwitchToNewTask
{
bool withContext = false;
public WaitForSwitchToNewTask(bool _with)
{
withContext = _with;
}
public ConfiguredTaskAwaitable.ConfiguredTaskAwaiter GetAwaiter()
{
return Task.Run(() => { }).ConfigureAwait(withContext).GetAwaiter();
}
}
public sealed class SynchronizationContextAwaiter : INotifyCompletion
{
private readonly SynchronizationContext _context;
public SynchronizationContextAwaiter(SynchronizationContext context) =>
_context = context ?? throw new ArgumentNullException("context");
public bool IsCompleted => SynchronizationContext.Current == _context;
public void OnCompleted(Action action)
{
if (_context == null)
{
action();
}
else
{
_context.Send(x => action(), null);
}
}
public void GetResult() { }
}
}
总结
UI线程切换是多线程编程中的重要环节,掌握它对于开发高效、稳定的用户界面至关重要。
本文从UI线程切换的核心思路出发,详细介绍了在不同.NET版本中开启新任务、回到UI线程的方法,并深入探讨了使用SynchronizationContext和await进行高级线程切换的技巧。
最后,通过整合这些方法到一个类中,提供了更加便捷、统一的线程管理方式。希望本文能帮助大家更好地理解和掌握UI线程切换技术,提升开发效率和质量。
关键词
UI线程切换、多线程编程、WPF、SynchronizationContext、Task、线程切换、UI线程、Invoke、Dispatcher、Task、BeginInvoke、C#、多线程、WinForm
最后
如果你觉得这篇文章对你有帮助,不妨点个赞支持一下!你的支持是我继续分享知识的动力。如果有任何疑问或需要进一步的帮助,欢迎随时留言。
也可以加入微信公众号 [DotNet技术匠] 社区,与其他热爱技术的同行一起交流心得,共同成长!
优秀是一种习惯,欢迎大家留言学习!
作者:咖喱gg
出处:cnblogs.com/gxrsprite/p/12321759.html
声明:网络内容,仅供学习,尊重版权,侵权速删,歉意致谢!