开发Visual Studio扩展时设置剪贴板内容遇异常,求优化方案
在Visual Studio Extensibility扩展中优雅写入剪贴板的解决方案
问题背景
我开发了一个Visual Studio扩展,基于Windows.ApplicationModel.DataTransfer.Clipboard的剪贴板历史生成Git分支名——读取历史前两项内容组合后写回剪贴板。读取逻辑正常,但调用Clipboard.SetContent(dataPackage)时始终抛出WinRT异常,尝试用Task.Run切换线程、改用System.Windows.Clipboard等方式,只有后者配合STATask.RunAsync能工作,但觉得不够优雅。
环境信息:
- 开发框架:Visual Studio Extensibility
- 语言:C#
- 目标框架:.NET 8.0
- 目标系统:Windows 10.0.22621.0
报错代码与异常信息
首次尝试的核心代码:
public override async Task ExecuteCommandAsync(IClientContext context, CancellationToken cancellationToken) { try { var clipItems = (await Clipboard.GetHistoryItemsAsync()).Items.Take(2).ToArray(); var taskNumber = (await clipItems[1].Content.GetTextAsync()).ToString().Trim(); var taskName = (await clipItems[0].Content.GetTextAsync()).ToString().Trim(); var branchName = $"feature/{taskNumber}-{taskName}"; DataPackage dataPackage = new(); dataPackage.RequestedOperation = DataPackageOperation.Copy; dataPackage.SetText(branchName); Clipboard.SetContent(dataPackage); // 此处抛出异常 } catch (Exception ex) { await Extensibility.Shell().ShowPromptAsync(ex.Message, PromptOptions.OK, cancellationToken); } }
异常堆栈:
at WinRT.ExceptionHelpers.<ThrowExceptionForHR>g__Throw|39_0(Int32 hr) at WinRT.ExceptionHelpers.ThrowExceptionForHR(Int32 hr) at ABI.Windows.ApplicationModel.DataTransfer.IClipboardStaticsMethods.SetContent(IObjectReference _obj, DataPackage content) at Windows.ApplicationModel.DataTransfer.Clipboard.SetContent(DataPackage content) at BranchNameExtension.CommandBranchName.<ExecuteCommandAsync>d__5.MoveNext() in C:\repos\_My Apps\_Extensions\VS Extensions 2 Solution\BranchNameExtension\CommandBranchName.cs:line 59
尝试用Task.Run切换线程后仍报错,最终用以下代码实现需求但不够优雅:
public override async Task ExecuteCommandAsync(IClientContext context, CancellationToken cancellationToken) { try { var clipItems = (await Clipboard.GetHistoryItemsAsync()).Items.Take(2).ToArray(); var taskNumber = (await clipItems[1].Content.GetTextAsync()).ToString().Trim(); var taskName = (await clipItems[0].Content.GetTextAsync()).ToString().Trim(); var branchName = $"feature/{taskNumber}-{taskName}"; await STATask.RunAsync(() => System.Windows.Clipboard.SetText(branchName)); } catch (Exception ex) { throw; } }
优雅解决方案:利用VS Extensibility的UI线程调度
问题根源在于:Windows.ApplicationModel.DataTransfer.Clipboard.SetContent需要在STA线程执行,而VS Extensibility的命令执行上下文默认可能不是合适的STA线程,直接用Task.Run会切换到MTA线程,依然不满足要求。
正确的做法是借助VS Extensibility提供的UIThread调度器,确保代码在STA的UI线程上执行:
using Microsoft.VisualStudio.Extensibility.UI; public override async Task ExecuteCommandAsync(IClientContext context, CancellationToken cancellationToken) { try { // 读取剪贴板历史逻辑保持不变 var clipItems = (await Clipboard.GetHistoryItemsAsync()).Items.Take(2).ToArray(); var taskNumber = (await clipItems[1].Content.GetTextAsync()).ToString().Trim(); var taskName = (await clipItems[0].Content.GetTextAsync()).ToString().Trim(); var branchName = $"feature/{taskNumber}-{taskName}"; // 使用VS Extensibility的UIThread调度器执行剪贴板写入 await Extensibility.UIThread().InvokeAsync(() => { DataPackage dataPackage = new(); dataPackage.RequestedOperation = DataPackageOperation.Copy; dataPackage.SetText(branchName); Clipboard.SetContent(dataPackage); // 可选:刷新剪贴板确保内容生效 Clipboard.Flush(); }, cancellationToken); } catch (Exception ex) { await Extensibility.Shell().ShowPromptAsync(ex.Message, PromptOptions.OK, cancellationToken); } }
方案说明
Extensibility.UIThread():VS Extensibility提供的线程调度器,会将委托调度到Visual Studio的主UI线程(天然是STA线程),完美匹配Clipboard.SetContent的线程要求。Clipboard.Flush():可选调用,确保剪贴板内容持久化,即使应用退出后内容依然保留,适合需要粘贴到其他窗口的场景。
替代方案:使用Windows.ApplicationModel.DataTransfer.Clipboard.SetTextAsync
如果只需要写入文本内容,还可以使用更简洁的SetTextAsync方法,该方法内部已处理线程适配:
public override async Task ExecuteCommandAsync(IClientContext context, CancellationToken cancellationToken) { try { var clipItems = (await Clipboard.GetHistoryItemsAsync()).Items.Take(2).ToArray(); var taskNumber = (await clipItems[1].Content.GetTextAsync()).ToString().Trim(); var taskName = (await clipItems[0].Content.GetTextAsync()).ToString().Trim(); var branchName = $"feature/{taskNumber}-{taskName}"; // 直接调用异步SetTextAsync,无需手动处理线程 await Clipboard.SetTextAsync(branchName); } catch (Exception ex) { await Extensibility.Shell().ShowPromptAsync(ex.Message, PromptOptions.OK, cancellationToken); } }
方案说明
Clipboard.SetTextAsync是WinRT提供的异步方法,内部自动处理线程上下文,无需额外调度,代码更简洁,适合仅写入文本的场景。
内容的提问来源于stack exchange,提问作者nfrick
相关产品推荐
相关产品推荐

