临界区与 lock 关键字
核心作用:
通过将多线程访问串行化,保护共享资源或代码段。lock 关键字是 Monitor 类的语法糖,提供异常安全的临界区实现。
实现示例:
// 创建私有静态只读对象 // private static readonly object _lockObj = new object(); private static readonly System.Threading.Lock _locker = new(); // .NET 9+ 推荐使用 Lock 类型,避免传统 object 的性能损耗 public void ThreadSafeMethod() { lock (_lockObj) { // 临界区代码(每次仅一个线程可进入) } }
超时机制:
高并发场景可结合 Monitor.TryEnter 设置超时,避免无限等待:
if (Monitor.TryEnter(lockObject, TimeSpan.FromSeconds(1))) { try { /* 操作 */ } finally { Monitor.Exit(lockObject); } }
关键特性:
用户态锁(无内核切换开销)
自动调用Monitor.Enter和Monitor.Exit
必须使用专用私有对象作为锁标识
注意事项:
❌ 避免锁定this、Type实例或字符串(易引发死锁)
❌ 避免嵌套锁(需严格按顺序释放)
✅ 推荐readonly修饰锁对象
❌ lock 不适用于异步代码(async/await),需使用 SemaphoreSlim 实现异步锁
互斥锁(Mutex)
核心作用:
系统级内核锁,支持跨进程同步,但性能开销较高(用户态/内核态切换)。
实现示例:
using var mutex = new Mutex(false, "Global\\MyAppMutex"); try { // 等待锁(最大等待时间500ms) if (mutex.WaitOne(500)) { // 临界区代码 } } finally { if (mutex != null) { mutex.ReleaseMutex(); } }
关键特性:
支持跨应用程序域同步
线程终止时自动释放锁
支持命名互斥体(系统全局可见)
适用场景:
单实例应用程序控制
进程间共享文件访问
硬件设备独占访问
信号量(Semaphore)
核心作用:
通过许可计数器控制并发线程数,SemaphoreSlim为轻量级版本(用户态实现)。
实现对比:
类型 跨进程 性能 最大许可数 Semaphore ✔️ 低 系统限制 SemaphoreSlim ❌ 高 Int32.Max 代码示例:
// 创建初始3许可、最大5许可的信号量 var semaphore = new SemaphoreSlim(3, 5); semaphore.Wait(); // 获取许可 try { // 资源访问代码 } finally { semaphore.Release(); }
异步编程
private readonly SemaphoreSlim _asyncLock = new(1, 1); public async Task UpdateAsync() { await _asyncLock.WaitAsync(); try { /* 异步操作 */ } finally { _asyncLock.Release(); } }
典型应用:
数据库连接池(限制最大连接数)
API 请求限流
批量任务并发控制
事件(Event)
核心机制:
通过信号机制实现线程间通知,分为两种类型:
类型 信号重置方式 唤醒线程数 AutoResetEvent 自动 单个 ManualResetEvent 手动 所有 使用示例:
var autoEvent = new AutoResetEvent(false); // 等待线程 Task.Run(() => { autoEvent.WaitOne(); // 收到信号后执行 }); // 信号发送线程 autoEvent.Set(); // 唤醒一个等待线程
高级用法:
配合WaitHandle.WaitAll实现多事件等待
使用ManualResetEventSlim提升性能
读写锁(ReaderWriterLockSlim)
核心优势:
实现读写分离的并发策略,适合读多写少场景(如缓存系统)。
锁模式对比:
模式 并发性 升级支持 读模式(EnterReadLock) 多线程并发读 ❌ 写模式(EnterWriteLock) 独占访问 ❌ 可升级模式 单线程读→写 ✔️ 代码示例:
var rwLock = new ReaderWriterLockSlim(); // 读操作 rwLock.EnterReadLock(); try { // 只读访问 } finally { rwLock.ExitReadLock(); } // 写操作 rwLock.EnterWriteLock(); try { // 排他写入 } finally { rwLock.ExitWriteLock(); }
最佳实践:
优先使用ReaderWriterLockSlim(旧版有死锁风险)
避免长时间持有读锁(可能饿死写线程)
原子操作(Interlocked)
原理:
通过CPU指令实现无锁线程安全操作。
常用方法:
int counter = 0; Interlocked.Increment(ref counter); // 原子递增 Interlocked.Decrement(ref counter); // 原子递减 Interlocked.CompareExchange(ref value, newVal, oldVal); // CAS操作
适用场景:
简单计数器
标志位状态切换
无锁数据结构实现
自旋锁(SpinLock)
核心特点:
通过忙等待(busy-wait)避免上下文切换,适用极短临界区(<1微秒)。
实现示例:
private SpinLock _spinLock = new SpinLock(); public void CriticalOperation() { bool lockTaken = false; try { _spinLock.Enter(ref lockTaken); // 极短临界区代码 } finally { if (lockTaken) _spinLock.Exit(); } }
优化技巧:
单核CPU需调用Thread.SpinWait或Thread.Yield
配合SpinWait结构实现自适应等待
同步机制对比指南
机制 跨进程 开销级别 最佳适用场景 lock ❌ 低 通用临界区保护 Mutex ✔️ 高 进程间资源独占 Semaphore ✔️ 中 并发数限制(跨进程) SemaphoreSlim ❌ 低 并发数限制(进程内) ReaderWriterLockSlim ❌ 中 读多写少场景 SpinLock ❌ 极低 纳秒级临界区 Interlocked - 无锁 简单原子操作
选择原则:
优先考虑用户态锁(lock/SpinLock/SemaphoreSlim)
跨进程需求必须使用内核对象(Mutex/Semaphore)
读写比例超过10:1时考虑读写锁
自旋锁仅用于高频短操作(如链表指针修改)
通过以上结构化的分类和对比,开发者可以更精准地选择适合特定场景的线程同步方案。建议在实际使用中配合性能分析工具(如BenchmarkDotNet)进行量化验证。
💡 ASP.NET 的异步编程(async/await)本质是单进程内的线程调度,不算“跨进程”。每个 IIS 应用程序池对应一个独立的工作进程(w3wp.exe),不同用户访问同一应用程序池下的 ASP.NET 网站,两者的请求均由同一个 w3wp.exe 进程处理。可能跨进程的场景有:Web Garden 配置、多应用程序池部署等。
在 C# 中,除了常规锁机制(如 lock、Mutex、Semaphore 等),还有一些内置类型通过内部锁或无锁设计实现线程安全。以下是常见的几类:
线程安全集合(System.Collections.Concurrent)这些集合通过细粒度锁或无锁算法(如 CAS)实现线程安全,适合高并发场景。
ConcurrentDictionary:分段锁机制,将数据分片存储,每个分片独立加锁,减少锁竞争。
ConcurrentQueue / ConcurrentStack:基于原子操作(Interlocked)保证线程安全。
ConcurrentBag:每个线程维护本地存储,减少争用,适合频繁添加和移除的场景。
BlockingCollection:基于 ConcurrentQueue 和信号量(SemaphoreSlim)实现生产-消费者模式,支持阻塞和超时。
不可变集合(System.Collections.Immutable) 通过数据不可变性实现线程安全(无需锁),每次修改返回新对象。
Lazy 的线程安全初始化(Lazy<T>) 通过锁或 Interlocked 确保延迟初始化的线程安全。
通道(System.Threading.Channels)用于异步生产-消费者模型,内部通过锁和信号量管理容量限制。
内存缓存(System.Runtime.Caching.MemoryCache)内部使用锁保护共享状态,确保线程安全。
原子操作类型(Interlocked 类、Volatile 关键字、Unsafe 类)通过 CPU 指令实现无锁线程安全。
其他同步工具(Barrier、CountdownEvent)虽然不是严格意义上的锁,但用于协调线程。

zoom | 显示 |
1 | 洲名 |
2 | 洲名 |
3 | 国名 |
4 | 国名、首都 |
5 | 省名 |
6 | 省名、省会 |
7 | 地级市名 |
8 | 地级市名、县级市名 |
9 | 地级市名、县级市名、部分街道/镇名 |
10 | 地级市名、县级市名、部分街道/镇名 |
11 | 地级市名、县级市名、部分街道/镇名、部分村名 |
12 | 地级市名、县级市名、部分街道/镇名、部分村名 |
13 | 地标 |
14 | 村名 |
15 | 小区名 |
16 | 建筑物轮廓 |
17 | 建筑物幢号 |
设置缩放级别:
map.centerAndZoom(new T.LngLat(108.95, 34.27), 4); // 默认显示整个中国地图、省级边界线
map.setMinZoom(2); // 世界地图
map.setMaxZoom(8); // 区/县/市

驾驶自动化等级与划分要素的关系
分级 | 名称 | 车辆横向和纵向运动控制 | 目标和事件探测与响应 | 动态驾驶任务接管 | 设计运行条件 |
0 级 | 应急辅助 | 驾驶员 | 驾驶员及系统 | 驾驶员 | 有限制 |
1 级 | 部分驾驶辅助 | 驾驶员和系统 | 驾驶员及系统 | 驾驶员 | 有限制 |
2 级 | 组合驾驶辅助 | 系统 | 驾驶员及系统 | 驾驶员 | 有限制 |
3 级 | 有条件自动驾驶 | 系统 | 系统 | 动态驾驶任务接管用户(接管后成为驾驶员) | 有限制 |
4 级 | 高度自动驾驶 | 系统 | 系统 | 系统 | 有限制 |
5 级 | 完全自动驾驶 | 系统 | 系统 | 系统 | 无限制* |
* 排除商业和法规因素等限制。 |
摘自:《汽车驾驶自动化分级》(GB/T 40429-2021)
部分车企驾驶安全与辅助、智能领航和泊车辅助系统简介
问界
新M5 全系搭载华为高阶智能驾驶系统,包含智驾领航辅助(NCA)、车道巡航辅助(LCC)、车道巡航辅助增强(LCC Plus)、全向防碰撞系统(CAS)、智能泊车辅助(APA)、遥控泊车辅助(RPA)、代客泊车辅助(AVP)、哨兵模式等功能。部分高阶智驾功能需付费开通,如城区智驾领航辅助(City NCA)、城区车道巡航辅助增强(City LCC Plus)、代客泊车辅助(AVP)等。
阿维塔
驾驶安全与辅助:
前向碰撞预警(FCW)、自动紧急制动(AEB)、异形障碍物自动紧急制动(GAEB)、低速自动紧急制动(LAEB)、前向横穿碰撞预警(FCTA)、前向横穿碰撞制动(FCTB)、后向碰撞预警(RCW)、后向横穿碰撞预警(RCTA)、后向横穿碰撞制动(RCTB)、后向自动紧急制动(RAEB)、交通标志识别(TSR)、交通信号灯识别(TLR)、超速告警(TSA)、开门预警(DOW)、车道偏离预警(LDW)、盲区监测预警(BSD)、车道保持辅助(LKA)、紧急车道保持辅助(ELKA)、侧向障碍物防碰撞(LOCP)、自适应巡航辅助(ACC)、高速车道巡航辅助 (Highway LCC)
智能领航系统:
高速智驾领航辅助 (Highway NCA)、城区车道巡航辅助 (City LCC)、城区智驾领航辅助 (City NCA)
智能泊车辅助系统:
智能泊车辅助(APA)、遥控泊车辅助(RPA)、代客泊车辅助(AVP)
其中,ADS高阶功能包需付费购买,包含城区智驾领航辅助 (City NCA)与代客泊车辅助(AVP)。
参考:阿维塔 12 参数配置表
蔚来
智能驾驶-安全辅助:
前向碰撞预警 (FCW)、自动紧急制动 (AEB)、车辆盲区监测 (BSD)、变道盲区预警 (LCA)、侧方开门预警 (DOW)、后方穿行预警带制动 (RCTA-B)、前方穿行预警带制动 (FCTA-B)、车道偏离预警 (LDW)、车道保持辅助 (LKA)、紧急主动停车(EAS)、紧急车道保持 (ELK)、全场景误加速抑制辅助 (MAI+)、增强型驾驶员感知系统 (ADMS)、通用障碍物预警及辅助 (GOA)、增强型自动紧急转向 (AES)
智能驾驶-泊车辅助:
视觉融合泊车辅助 (S-APA with Fusion)、换电站泊车辅助 (PSAP)、车辆近距召唤 (NBS)、遥控泊车辅助 (RPA)
智能驾驶-辅助驾驶:
车道居中辅助 (LCC)、转向灯控制变道 (ALC)、智能自适应巡航 (i-ACC)、道路标识识别 (TSR)、动态环境模拟显示 (ESD)、视觉融合起步提醒 (AGN)
蔚来智能驾驶NAD服务:
智能驾驶NAD服务(覆盖部分城区道路、封闭高速道路的智能驾驶体验)、低速及泊车智能驾驶NAD服务(支持领航泊车、智能召唤等功能体验)、全域领航辅助 (NOP+)(支持高速、城市快速路、城区道路、高速服务区领航换电等体验)
小米
Xiaomi Pilot Pro:高速领航辅助(NOA)、智能泊车辅助(APA)、车道居中辅助(LCC)、代客泊车辅助(AVP)
Xiaomi Pilot Max:城市领航辅助(NOA)
特斯拉
基本版 Autopilot 自动辅助驾驶:
主动巡航控制、自动辅助转向
增强版 Autopilot 自动辅助驾驶:
自动变道、自动辅助导航驾驶、自动泊车、召唤功能、智能召唤功能
完全自动驾驶功能 FSDFull-Self Driving(简称“FSD”):
交通信号灯和停车标志控制、在城市街道自动辅助转向等。2024年3月31日,特斯拉向美国部分用户推送FSD V12(Supervised)版本。

一般用法:/宽度/高度
例:https://picsum.photos/400/300
方形图像:/边长
指定图像:/id/{image}
例:https://picsum.photos/id/237/400/300
基于种子获取相同的随机图像:/seed/{seed}
例:https://picsum.photos/seed/xoyozo/400/300
灰度:?grayscale
例:https://picsum.photos/400/300?grayscale
模糊:?blur
例:https://picsum.photos/400/300/?blur
指定模糊级别:?blur=[1,10]
例:https://picsum.photos/400/300/?blur=2
组合
例:https://picsum.photos/id/870/400/300?grayscale&blur=2
防止缓存
例:
<img src="https://picsum.photos/400/300?random=1">
<img src="https://picsum.photos/400/300?random=2">
指定图像格式:

Linux 设置服务开机自动启动的方式有好多种,这里介绍一下通过 chkconfig 命令添加脚本为开机自动启动的方法。
编写脚本 ossftp(这里以开机启动 ossftp 服务为例),脚本内容如下:
#!/bin/sh #chkconfig: 2345 80 90 #description: 开机自动启动的脚本程序 # 开启 ossftp 服务 /root/ossftp-1.2.0-linux-mac/start.sh &
脚本第一行 “#!/bin/sh” 告诉系统使用的 shell;
脚本第二行 “#chkconfig: 2345 80 90” 表示在 2/3/4/5 运行级别启动,启动序号(S80),关闭序号(K90);
脚本第三行 表示的是服务的描述信息;
要执行的文件(示例中的 /root/ossftp-1.2.0-linux-mac/start.sh)必须设置“可执行”权限,命令结尾的“&”可使进程持续。
注意: 第二行和第三行必写,否则会出现如“服务 ossftp 不支持 chkconfig”这样的错误。
将写好的 ossftp 脚本移动到 /etc/rc.d/init.d/ 目录下
给脚本赋可执行权限
chmod +x /etc/rc.d/init.d/ossftp
添加脚本到开机自动启动项目中
chkconfig --add ossftp chkconfig ossftp on
执行命令“chkconfig --list”可列出开机启动的服务及当前的状态。
到这里就设置完成了,我们只需要重启一下我们的服务器,就能看到我们配置的 ossftp 服务已经可以开机自动启动了。
在 MozillaWiki 中推荐了三种配置,分别是现代兼容性、中级兼容性(默认)和旧的向后兼容性。
现代兼容性
对于不需要向后兼容性的服务,以下参数提供更高级别的安全性。 此配置与 Windows 7,Edge,Opera 17,Safari 9,Android 5.0 和 Java 8 上的 Firefox 27,Chrome 30,IE 11 兼容。
listen 443 ssl http2; ssl_protocols TLSv1.2; ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256;
中级兼容性(默认)
对于不需要与旧客户端(主要是 WinXP)兼容但仍需要支持各种客户端的服务,建议使用此配置。 它与 Firefox 1,Chrome 1,IE 7,Opera 5 和 Safari 1 兼容。(推荐)
listen 443 ssl http2; ssl_protocols TLSv1.2 TLSv1.1 TLSv1; ssl_ciphers ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS;
旧的向后兼容性
这是旧的密码组件,它主要工作在 Windows XP / IE6 中,如果不是特别需要,建议放弃此配置。
listen 443 ssl http2; ssl_protocols TLSv1.2 TLSv1.1 TLSv1 SSLv3; ssl_ciphers ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:DES-CBC3-SHA:HIGH:SEED:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!RSAPSK:!aDH:!aECDH:!EDH-DSS-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA:!SRP;
一般情况下,使用中级兼容性(默认)的配置即可启用 HTTP2,你还可以通过 mozilla 的 Server side TLS Tools 根据你的服务器进行详细配置。
获取
在 NuGet 中搜索 ZXing.Net
简单示例
var qr = new ZXing.QrCode.QRCodeWriter();
var matrix = qr.encode("http://xoyozo.net/", ZXing.BarcodeFormat.QR_CODE, 200, 200);
var writer = new ZXing.BarcodeWriter()
{
Format = ZXing.BarcodeFormat.QR_CODE
};
Bitmap bitmap = writer.Write(matrix);
扩展示例
string content = "http://xoyozo.net/";
var hints = new Dictionary<ZXing.EncodeHintType, object>();
hints.Add(ZXing.EncodeHintType.ERROR_CORRECTION, ZXing.QrCode.Internal.ErrorCorrectionLevel.H); // 纠错级别
hints.Add(ZXing.EncodeHintType.CHARACTER_SET, Encoding.Default.WebName); // 编码:gb2312
hints.Add(ZXing.EncodeHintType.MARGIN, 0); // 出血码元数(标准为 4,美观为 2)
var qr = new ZXing.QrCode.QRCodeWriter();
var matrix = qr.encode(content, ZXing.BarcodeFormat.QR_CODE, 200, 200, hints);
var writer = new ZXing.BarcodeWriter()
{
Format = ZXing.BarcodeFormat.QR_CODE,
Renderer = new ZXing.Rendering.BitmapRenderer
{
Foreground = Color.Black, // 前景色(默认黑色)
Background = Color.White, // 背景色(默认白色)
},
};
Bitmap bitmap = writer.Write(matrix);
将 Bitmap 写入到流
Stream stream = new MemoryStream();
bitmap.Save(stream, ImageFormat.Png);
将 Bitmap 保存到磁盘
string path = "D:\wwwroot\upload\abc.png";
bitmap.Save(path, ImageFormat.Png);
更多
对比 ThoughtWorks.QRCode 和 ZXing.Net

获取
在 NuGet 中搜索 ThoughtWorks.QRCode
简单示例
var qr = new ThoughtWorks.QRCode.Codec.QRCodeEncoder();
Bitmap bitmap = qr.Encode("http://xoyozo.net/");
扩展示例
string content = "http://xoyozo.net/";
var qr = new ThoughtWorks.QRCode.Codec.QRCodeEncoder
{
// 纠错级别,L (7%)、M (15%)、Q (25%)、H (30%)
QRCodeErrorCorrect = ThoughtWorks.QRCode.Codec.QRCodeEncoder.ERROR_CORRECTION.H,
// 码元尺寸(像素)
QRCodeScale = 4,
// 前景色(默认黑色)
QRCodeForegroundColor = Color.Black,
// 背景色(默认白色)
QRCodeBackgroundColor = Color.White,
};
// 根据内容确定 Mode,参:http://en.wikipedia.org/wiki/QR_code#Storage
if (Regex.IsMatch(content, @"^\d+$"))
{
qr.QRCodeEncodeMode = ThoughtWorks.QRCode.Codec.QRCodeEncoder.ENCODE_MODE.NUMERIC;
}
else if (Regex.IsMatch(content, @"^[0-9A-Z $%*+-./:]+$"))
{
qr.QRCodeEncodeMode = ThoughtWorks.QRCode.Codec.QRCodeEncoder.ENCODE_MODE.ALPHA_NUMERIC;
}
else
{
qr.QRCodeEncodeMode = ThoughtWorks.QRCode.Codec.QRCodeEncoder.ENCODE_MODE.BYTE;
}
Bitmap bitmap = qr.Encode(content, Encoding.Default); // 编码,简体中文系统默认为 gb2312
裁切掉多余的 1 像素
ThoughtWorks.QRCode 生成的四周没有留白的二维码图片,其右边和下边分别会多出 1 像素,使用以下方法来调整图片大小
// 创建一个新的图片(宽度和高度各缩小 1 像素)
Bitmap bitmap2 = new Bitmap(bitmap.Size.Width - 1, bitmap.Size.Height - 1);
// 以新图片来绘图
Graphics g2 = Graphics.FromImage(bitmap2);
// 新旧图片绘制到新图片中(左上角对齐)
g2.DrawImage(bitmap, 0, 0);
将 Bitmap 写入到流
Stream stream = new MemoryStream();
bitmap.Save(stream, ImageFormat.Png);
若已裁切 1 像素,请修改为 bitmap2
将 Bitmap 保存到磁盘
string path = "D:\wwwroot\upload\abc.png";
bitmap.Save(path, ImageFormat.Png);
若已裁切 1 像素,请修改为 bitmap2
更多
对比 ThoughtWorks.QRCode 和 ZXing.Net

ThoughtWorks.QRCode | ZXing.Net | |
生成方式 | 以指定的码元大小、版本、模式、纠错级别等信息来确定最终生成的图片大小 | 指定图片大小后,自动调整码元大小、出血* |
关于图片尺寸 | 不能直接确定最终生成的二维码图片的尺寸,可以先反向估算码元大小,再微调码元大小,直到不小于目标尺寸,如果必须严格限制尺寸,建议在 jpg 方式处理,因为 png 二维码的每个像素点非 0 即 1,在小尺寸的情况下会导致无法识别。(涉及到多次生成二维码,请斟酌性能消耗) | 生成二维码时即指定图片大小,但会留白,比较难以掌控实际效果 |
"BUG" | 右边和下边有多余 1 像素需要手动去除 | 虽然可以设置参数 EncodeHintType.MARGIN,但还是没有达到预期的效果(网上有解析原因,请自行搜索) |
…… |
*出血:为了提高二维码识别度,在生成的二维码四周留出若干码元(建议 4 个)空白。
更多

兼容建议:(2017年初)
连淘宝都放弃 IE6 了,就不需要再坚持了;
IE7 跟错了老大(Vista),已经没有市场了;
当前市场份额最大的操作系统还是 Win7,所以 IE8 是必须要兼容的,就算国人都安装了国产浏览器,内核也未必会升到 IE9-11。
当然每个站的访客群体不同,具体还得参考网站统计数据来确定兼容级别。
为了友好,建议你在不打算兼容的浏览器上提供升级提示和新版下载链接。
如果是纯移动端,那么大胆地用 HTML5 就行了。
以下例举我遇到过的兼容问题:
浏览器版本 | 注意事项 |
IE6-7 | <input type="radio" /> 必须设置 name 才能被选中 |
IE6-7 | 不支持 console.log |
IE6-7 | 不支持 JSON.stringify |
所有 IE | 不支持 <input /> 新的 type 类型,查看详情 |
