You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

开发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);
    }
}

方案说明

  1. Extensibility.UIThread():VS Extensibility提供的线程调度器,会将委托调度到Visual Studio的主UI线程(天然是STA线程),完美匹配Clipboard.SetContent的线程要求。
  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 11:42:02