Browse Source

打印问题解决了。

张洋 2 weeks ago
parent
commit
04bb5248e4

BIN
bin/Win32/Debug/zhipuzi_pos_windows/DuiLib_d.dll


BIN
bin/Win32/Release/zhipuzi_pos_windows/dump/2026-09-26-18-07-22.dmp


BIN
bin/Win32/Release/zhipuzi_pos_windows/zhipuzi_pos_windows.exe


BIN
dll/debug/DuiLib_d.dll


+ 102 - 29
zhipuzi_pos_windows/worker/CPosPrinterWorker.cpp

@@ -51,8 +51,8 @@ void CPosPrinterWorker::Stop()
 
 void CPosPrinterWorker::RefreshShouyinChannels()
 {
-	//设置变更后重建通道,若打印线程正在等待最慢端口,最多等一个超时周期
-	std::lock_guard<std::mutex> lock(m_shouyin_channels_mutex);
+	//设置变更后重建通道。容器锁在 StartShouyinChannels/StopShouyinChannels 内部加,
+	//这里不能持锁(否则与内部加锁自死锁,且会把 join 拖进临界区)
 	StartShouyinChannels();
 }
 
@@ -128,6 +128,9 @@ void CPosPrinterWorker::StartShouyinChannels()
 	}
 
 	//每个端口一个通道,打开失败的端口也保留通道,等待热插拔重试
+	//先建到局部,最后一次性挂进 m_shouyin_channels(加容器锁),避免“半成品”被发送线程看到
+	std::vector<std::shared_ptr<CPortSendChannel>> new_channels;
+
 	for (std::vector<std::pair<std::wstring, PrinterPortKind>>::iterator it = ports.begin(); it != ports.end(); it++)
 	{
 		std::shared_ptr<CPortSendChannel> ch = std::make_shared<CPortSendChannel>();
@@ -137,27 +140,52 @@ void CPosPrinterWorker::StartShouyinChannels()
 		OpenChannelHandle(ch);
 
 		ch->worker = std::thread(&CPosPrinterWorker::ChannelWorker, this, ch);
-		m_shouyin_channels.push_back(ch);
+		new_channels.push_back(ch);
+	}
+
+	{
+		std::lock_guard<std::mutex> lock(m_shouyin_channels_mutex);
+		for (std::vector<std::shared_ptr<CPortSendChannel>>::iterator it = new_channels.begin(); it != new_channels.end(); it++)
+		{
+			m_shouyin_channels.push_back(*it);
+		}
 	}
 }
 
 void CPosPrinterWorker::StopShouyinChannels()
 {
-	for (std::vector<std::shared_ptr<CPortSendChannel>>::iterator it = m_shouyin_channels.begin(); it != m_shouyin_channels.end(); it++)
+	//先把通道整体摘出来:join 绝不能发生在持有 m_shouyin_channels_mutex 的时候,
+	//否则发送线程(SendDataToShouyinPirnter 持该锁)会被长时间挡住
+	std::vector<std::shared_ptr<CPortSendChannel>> channels;
+	{
+		std::lock_guard<std::mutex> lock(m_shouyin_channels_mutex);
+		channels.swap(m_shouyin_channels);
+	}
+
+	//通知所有通道退出(stop 与 worker 的读取用 ch->mtx 串行)
+	for (std::vector<std::shared_ptr<CPortSendChannel>>::iterator it = channels.begin(); it != channels.end(); it++)
+	{
+		std::lock_guard<std::mutex> lock((*it)->mtx);
+		(*it)->stop = true;
+	}
+
+	//等线程真正退出:worker 是短睡轮询,最多一个 Sleep 周期就返回
+	for (std::vector<std::shared_ptr<CPortSendChannel>>::iterator it = channels.begin(); it != channels.end(); it++)
 	{
 		std::shared_ptr<CPortSendChannel> ch = *it;
-		{
-			std::lock_guard<std::mutex> lock(ch->mtx);
-			ch->stop = true;
-		}
-		ch->cv.notify_one();
 		if (ch->worker.joinable())
 		{
+			HANDLE hThread = ch->worker.native_handle();
+			while (::WaitForSingleObject(hThread, 10) == WAIT_TIMEOUT)
+			{
+				//尽力取消该线程正在进行的同步WriteFile;若驱动不支持取消,仍需等待线程返回
+				::CancelSynchronousIo(hThread);
+			}
 			ch->worker.join();
 		}
 	}
 
-	m_shouyin_channels.clear();
+	//channels 出作用域:shared_ptr 归零,端口句柄已由 worker 自身收尾关闭
 }
 
 void CPosPrinterWorker::ChannelWorker(std::shared_ptr<CPortSendChannel> ch)
@@ -166,14 +194,28 @@ void CPosPrinterWorker::ChannelWorker(std::shared_ptr<CPortSendChannel> ch)
 	{
 		std::string data;
 		{
-			std::unique_lock<std::mutex> lock(ch->mtx);
-			ch->cv.wait(lock, [&ch] { return ch->stop || !ch->queue.empty(); });
-			if (ch->stop)
+			//非阻塞取任务:抢不到锁就下一轮再看,绝不在锁上/条件变量上等待
+			std::unique_lock<std::mutex> lock(ch->mtx, std::try_to_lock);
+			if (lock.owns_lock())
 			{
-				break;
+				if (ch->stop)
+				{
+					break;
+				}
+
+				if (!ch->queue.empty())
+				{
+					data = ch->queue.front();
+					ch->queue.pop();
+				}
 			}
-			data = ch->queue.front();
-			ch->queue.pop();
+		}
+
+		if (data.empty())
+		{
+			//没有数据(或暂时拿不到锁):短睡后再来,不阻塞其他端口
+			::Sleep(20);
+			continue;
 		}
 
 		//句柄未打开(设备拔掉或被占用),尝试重新打开
@@ -186,9 +228,16 @@ void CPosPrinterWorker::ChannelWorker(std::shared_ptr<CPortSendChannel> ch)
 			}
 		}
 
-		//慢端口超时只卡本通道的线程,不影响其他端口
+		//同步写出:数据写进驱动缓冲即返回,不做超时等待
 		DWORD dwWrite = 0;
-		bool ret = WriteWithTimeout(ch->hPort, data.c_str(), (DWORD)data.length(), 5000, dwWrite);
+		BOOL ret = ::WriteFile(ch->hPort, data.c_str(), (DWORD)data.length(), &dwWrite, NULL);
+		{
+			std::lock_guard<std::mutex> lock(ch->mtx);
+			if (ch->stop)
+			{
+				break;
+			}
+		}
 
 		if (!ret)
 		{
@@ -200,8 +249,23 @@ void CPosPrinterWorker::ChannelWorker(std::shared_ptr<CPortSendChannel> ch)
 
 			if (OpenChannelHandle(ch))
 			{
+				{
+					std::lock_guard<std::mutex> lock(ch->mtx);
+					if (ch->stop)
+					{
+						break;
+					}
+				}
+
 				dwWrite = 0;
-				ret = WriteWithTimeout(ch->hPort, data.c_str(), (DWORD)data.length(), 5000, dwWrite);
+				ret = ::WriteFile(ch->hPort, data.c_str(), (DWORD)data.length(), &dwWrite, NULL);
+				{
+					std::lock_guard<std::mutex> lock(ch->mtx);
+					if (ch->stop)
+					{
+						break;
+					}
+				}
 			}
 		}
 
@@ -231,7 +295,8 @@ bool CPosPrinterWorker::OpenChannelHandle(std::shared_ptr<CPortSendChannel> ch)
 	}
 	else
 	{
-		hPort = CreateFile(ch->portPath.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
+		//同步句柄(不带 FILE_FLAG_OVERLAPPED),WriteFile 写进驱动缓冲即返回,无需超时等待
+		hPort = CreateFile(ch->portPath.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
 	}
 
 	if (hPort == INVALID_HANDLE_VALUE || hPort == NULL)
@@ -263,7 +328,8 @@ bool CPosPrinterWorker::OpenChannelHandle(std::shared_ptr<CPortSendChannel> ch)
 
 HANDLE CPosPrinterWorker::OpenComPort(const std::wstring& com_path)
 {
-	HANDLE hPort = CreateFile(com_path.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
+	//同步句柄(不带 FILE_FLAG_OVERLAPPED),WriteFile 写进驱动缓冲即返回,无需超时等待
+	HANDLE hPort = CreateFile(com_path.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
 
 	if (hPort == INVALID_HANDLE_VALUE)
 	{
@@ -285,7 +351,7 @@ HANDLE CPosPrinterWorker::OpenComPort(const std::wstring& com_path)
 	LOG_INFO(com_path.c_str() << " success, handle:" << hPort);
 
 	//佳博的串口打印机,默认是这个波特率
-	DCB dcb;
+	DCB dcb = {};
 	dcb.DCBlength = sizeof(dcb);
 	GetCommState(hPort, &dcb);
 	dcb.BaudRate = 19200;
@@ -298,12 +364,13 @@ HANDLE CPosPrinterWorker::OpenComPort(const std::wstring& com_path)
 	}
 
 	//设定通讯端口超时参数
+	//写超时固定总量1秒(不按字节累加),避免大数据量时WriteFile长时间阻塞
 	COMMTIMEOUTS tmouts;
 	tmouts.ReadIntervalTimeout = 100;
 	tmouts.ReadTotalTimeoutMultiplier = 100;
 	tmouts.ReadTotalTimeoutConstant = 100;
-	tmouts.WriteTotalTimeoutConstant = 100;
-	tmouts.WriteTotalTimeoutMultiplier = 100;
+	tmouts.WriteTotalTimeoutConstant = 1000;
+	tmouts.WriteTotalTimeoutMultiplier = 0;
 	SetCommTimeouts(hPort, &tmouts);
 
 	//设置端口缓冲
@@ -479,11 +546,8 @@ void CPosPrinterWorker::SendDataToShouyinPirnter(std::string data)
 		for (std::vector<std::shared_ptr<CPortSendChannel>>::iterator it = m_shouyin_channels.begin(); it != m_shouyin_channels.end(); it++)
 		{
 			std::shared_ptr<CPortSendChannel> ch = *it;
-			{
-				std::lock_guard<std::mutex> ch_lock(ch->mtx);
-				ch->queue.push(data);
-			}
-			ch->cv.notify_one();
+			std::lock_guard<std::mutex> ch_lock(ch->mtx);
+			ch->queue.push(data);
 		}
 
 		return;
@@ -984,7 +1048,12 @@ bool CPosPrinterWorker::WriteWithTimeout(HANDLE hPort, const void* buffer, DWORD
 			}
 			else
 			{
+				//超时:请求取消后必须等这次 I/O 真正收尾再释放 OVERLAPPED,
+				//否则完成状态会写回已经出栈的栈帧,可能写坏上层数据
 				CancelIoEx(hPort, &ov);
+				WaitForSingleObject(ov.hEvent, INFINITE);
+				DWORD dwDummy = 0;
+				GetOverlappedResult(hPort, &ov, &dwDummy, FALSE);
 				ret = FALSE;
 			}
 		}
@@ -1026,7 +1095,11 @@ bool CPosPrinterWorker::ReadWithTimeout(HANDLE hPort, void* buffer, DWORD buffer
 			}
 			else
 			{
+				//超时:请求取消后必须等这次 I/O 真正收尾再释放 OVERLAPPED(同上)
 				CancelIoEx(hPort, &ov);
+				WaitForSingleObject(ov.hEvent, INFINITE);
+				DWORD dwDummy = 0;
+				GetOverlappedResult(hPort, &ov, &dwDummy, FALSE);
 				ret = FALSE;
 			}
 		}

+ 3 - 2
zhipuzi_pos_windows/worker/CPosPrinterWorker.h

@@ -2,8 +2,8 @@
 
 #include "../print/CPosPrinterData.h"
 
-#include <condition_variable>
 #include <memory>
+#include <mutex>
 #include <thread>
 
 //端口的物理类型,用于热插拔后重开句柄时区分配置方式
@@ -22,7 +22,8 @@ struct CPortSendChannel
 	PrinterPortKind portKind = PrinterPortKind::Usb;
 	std::queue<std::string> queue;
 	std::mutex mtx;
-	std::condition_variable cv;
+	//不用条件变量:worker 采用短睡轮询(与 HandleShouyinPrinter 等一致),
+	//避免任何"阻塞等待-唤醒"路径上的崩溃
 	bool stop = false;
 	std::thread worker;
 };