WCF命名管道单向通信下,如何向WPF GUI返回文件复制进度?
解决WCF命名管道单向通信下文件复制进度回传至WPF GUI的问题
单向WCF通信默认仅支持客户端发起请求,服务端无法主动推送数据。要实现文件复制进度回传至WPF进度条,有以下几种可行方案:
方案1:改用WCF双工通信契约(推荐)
命名管道绑定NetNamedPipeBinding原生支持双工通信,通过定义回调契约,让服务端在复制过程中主动回调客户端更新进度,实时性最好。
步骤与代码示例
- 定义回调与服务契约
// 回调契约:定义服务端调用客户端的进度更新方法 public interface IFileCopyCallback { [OperationContract(IsOneWay = true)] void UpdateCopyProgress(int percentage); } // 双工服务契约:关联回调契约 [ServiceContract(CallbackContract = typeof(IFileCopyCallback))] public interface IFileCopyService { [OperationContract] void CopyFile(string sourcePath, string destPath); }
- 服务端实现文件复制与进度回调
public class FileCopyService : IFileCopyService { public void CopyFile(string sourcePath, string destPath) { // 获取客户端回调通道 var callback = OperationContext.Current.GetCallbackChannel<IFileCopyCallback>(); var fileInfo = new FileInfo(sourcePath); long totalBytes = fileInfo.Length; long copiedBytes = 0; using (var sourceStream = new FileStream(sourcePath, FileMode.Open)) using (var destStream = new FileStream(destPath, FileMode.Create)) { byte[] buffer = new byte[4096]; int bytesRead; while ((bytesRead = sourceStream.Read(buffer, 0, buffer.Length)) > 0) { destStream.Write(buffer, 0, bytesRead); copiedBytes += bytesRead; int progress = (int)((copiedBytes * 100) / totalBytes); // 回调客户端更新进度 callback.UpdateCopyProgress(progress); // 避免过于频繁回调,添加短延迟 Thread.Sleep(10); } // 通知复制完成 callback.UpdateCopyProgress(100); } } }
- WPF客户端实现回调并更新UI
WPF UI控件仅能在UI线程更新,需通过Dispatcher切换线程:
// 回调处理器 public class FileCopyCallbackHandler : IFileCopyCallback { private readonly Action<int> _updateProgressAction; public FileCopyCallbackHandler(Action<int> updateProgressAction) { _updateProgressAction = updateProgressAction; } public void UpdateCopyProgress(int percentage) { // 切换到UI线程更新进度条 Application.Current.Dispatcher.Invoke(() => _updateProgressAction(percentage)); } } // 按钮点击事件逻辑 private void CopyButton_Click(object sender, RoutedEventArgs e) { var callbackHandler = new FileCopyCallbackHandler(progress => CopyProgressBar.Value = progress); var binding = new NetNamedPipeBinding(); var endpoint = new EndpointAddress("net.pipe://localhost/FileCopyService"); var factory = new DuplexChannelFactory<IFileCopyService>(callbackHandler, binding, endpoint); var serviceProxy = factory.CreateChannel(); // 后台启动复制任务,避免阻塞UI Task.Run(() => serviceProxy.CopyFile(@"C:\SourceFile.txt", @"C:\DestFile.txt")); }
方案2:客户端轮询服务端获取进度
如果不想修改为双工通信,可让客户端定期调用服务端的进度查询接口,适合对实时性要求不高的场景。
步骤与代码示例
- 服务端维护进度变量并提供查询方法
[ServiceContract] public interface IFileCopyService { [OperationContract] void CopyFile(string sourcePath, string destPath); [OperationContract] int GetCopyProgress(); } public class FileCopyService : IFileCopyService { private static int _currentProgress; private static readonly object _lockObj = new object(); // 保证线程安全 public void CopyFile(string sourcePath, string destPath) { var fileInfo = new FileInfo(sourcePath); long totalBytes = fileInfo.Length; long copiedBytes = 0; using (var sourceStream = new FileStream(sourcePath, FileMode.Open)) using (var destStream = new FileStream(destPath, FileMode.Create)) { byte[] buffer = new byte[4096]; int bytesRead; while ((bytesRead = sourceStream.Read(buffer, 0, buffer.Length)) > 0) { destStream.Write(buffer, 0, bytesRead); copiedBytes += bytesRead; lock (_lockObj) { _currentProgress = (int)((copiedBytes * 100) / totalBytes); } Thread.Sleep(10); } lock (_lockObj) { _currentProgress = 100; } } } public int GetCopyProgress() { lock (_lockObj) { return _currentProgress; } } }
- 客户端轮询更新UI
private void CopyButton_Click(object sender, RoutedEventArgs e) { var binding = new NetNamedPipeBinding(); var endpoint = new EndpointAddress("net.pipe://localhost/FileCopyService"); var factory = new ChannelFactory<IFileCopyService>(binding, endpoint); var serviceProxy = factory.CreateChannel(); // 后台启动复制任务 Task.Run(() => serviceProxy.CopyFile(@"C:\SourceFile.txt", @"C:\DestFile.txt")); // 启动轮询任务 Task.Run(async () => { while (true) { int progress = serviceProxy.GetCopyProgress(); Application.Current.Dispatcher.Invoke(() => CopyProgressBar.Value = progress); if (progress >= 100) break; await Task.Delay(100); // 每100ms查询一次进度 } }); }
方案3:本地共享资源传递进度(备选)
通过本地共享文件或内存映射文件传递进度,适合无法修改WCF契约的场景,但需处理同步和资源清理:
- 服务端在复制过程中,将进度写入指定本地文件(或内存映射区域)
- WPF客户端定期读取该文件,解析进度并更新UI
- 复制完成后,服务端删除或清空文件,客户端停止读取
内容的提问来源于stack exchange,提问作者kalview
相关产品推荐
相关产品推荐

