visualstudio绘画装饰与懒惰的更新
本文关键字:更新 绘画 visualstudio | 更新日期: 2023-09-27 18:21:55
我正在制作一个Visual Studio装饰扩展。如果至少2秒没有用户输入,我想更新装饰。所以我构建了一个worker,并试图删除和添加装饰,但VS说它不能更新,因为非ui线程调用了它。所以我在没有线程的情况下等待,然后我的编辑器变得非常滞后(因为ui线程在等待)
我想知道是否有一种方法可以通过延迟更新来更新装饰。绘制装饰是通过调用AddAdlement()来完成的,我找不到如何调用ui线程来绘制。
下面是我的代码
internal async void OnLayoutChanged(object sender, TextViewLayoutChangedEventArgs e)
{
Print("OnLayoutChanged Called");
task = Task.Factory.StartNew(() =>
{
Print("task Started");
if (e.NewSnapshot != e.OldSnapshot)
{
parseStopwatch.Restart();
shouldParse = true;
}
ParseWork(e);
});
await task;
}
private async void ParseWork(object param)
{
var e = (TextViewLayoutChangedEventArgs)param;
if (e == null)
{
shouldParse = false;
parseStopwatch.Stop();
CsharpRegionParser.ParseCs(this.view.TextSnapshot);
DrawRegionBox();
return;
}
while (shouldParse)
{
Task.Delay(10);
if ((shouldParse && parseStopwatch.ElapsedMilliseconds > 2000) || parseStopwatch.ElapsedMilliseconds > 5000)
{
break;
}
}
shouldParse = false;
parseStopwatch.Stop();
CsharpRegionParser.ParseCs(this.view.TextSnapshot);
DrawRequest(e);
return;
}
我不知道你为什么被否决,特别是因为在处理扩展时这是一个有趣的问题。
因此,针对您的第一个问题:Visual Studio具有与WPF相同的要求(由于其COM依赖性,增加了一些复杂性)。当您不在主(UI)线程上时,无法更新UI元素。不幸的是,如果你直接投入并使用你在WPF中使用的策略来处理它,你将经历另一个问题的世界(主要是死锁)。
首先,复习一下如何在VisualStudio扩展中处理从后台线程到UI线程的切换。我发现使用JoinableTaskFactory在VS中进行异步和多线程编程有助于解释。
我不得不用一个昂贵的解析操作做一些类似的事情。这是非常直接的。
我的解析器作为IViewModelTagger
实例的一部分执行,并使用以下序列(大致):
- 它使用
async void
事件处理程序订阅ITextBuffer.ChangedLowPriority
事件 - 一旦启动,它就会通过
CancellationToken.Cancel()
调用取消正在进行的任何解析操作。取消令牌被传递到所有支持它的东西中(在Roslyn中,它在任何你想要的地方都得到支持) - 它开始解析操作,但在开始之前,我有一个
Task.Delay(200, m_cancellationToken)
调用。基于我的打字速度和Roslyn的操作对任何昂贵的东西都有CancellationToken
重载的事实,我的200毫秒(我的解析工作也很轻)。YMMV
我使用的WPF组件非常需要UI线程,并且它们混合在IViewModelTagger
和IWpfTextViewListener
中。它们足够轻,我本可以跳过异步处理,但在非常大的类上,它们可以挂起UI。
为了处理这个问题,我做了以下操作:
- 在TextViewLayoutChanged上,我订阅了一个
async void
事件处理程序 - 我先
Task.Run()
昂贵的操作,防止UI被阻塞 - 当我最终创建WPF UI元素并将其作为装饰完成添加时(以及SDK中需要它的几个操作),我
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync()
来获得UI线程
我提到了"其他SDK操作",这很重要。在SDK中,除了主线程之外,还有几件事你不能在任何东西上做(内存现在让我很失望,但如果在后台线程上访问TextView的某些部分,它们尤其会失败,而且不会始终如一)。
有更多的选项用于执行UI线程的工作(普通Task.Run
工作,以及ThreadHelper.JoinableTaskFactory.Run
工作)。在我回答的早些时候,链接到Andrew Arnott的帖子解释了所有的选择。你会想完全理解这一点,因为根据任务的不同,有理由使用一些而不是其他。
希望能有所帮助!
Task.Delay
返回一个任务,该任务在延迟时完成。如果你这样称呼它而忽略结果,它并没有像你想象的那样。您可能想要的不是像以前那样调用Task.Factory.StartNew,而是:
var cancellationTokenSource = new CancellationTokenSource();
Task.Delay(2000, cancellationTokenSource.Token).ContinueWith(() => DoWork(), cancellationTokenSource.Token, TaskScheduler.Current).
这实际上意味着"启动一个等待2秒的计时器,然后一旦它完成,就在UI线程上运行DoWork方法。如果发生了更多的键入,那么你可以调用cancellTokenSource.Cancel(),然后再次运行。
此外,我必须询问您的类型"CSharpRegionParser"。如果您需要区域信息,并且使用的是Visual Studio 2015,那么您可以从Roslyn获得语法树,并且您应该关注工作区更改事件,而不是挂钩LayoutChanged。你最好把你的系统构建成一对标签工/装饰经理,因为写起来可能更清楚。。。我不清楚为什么要在LayoutChanged中进行解析逻辑,因为LayoutChanged是在视觉布局过程中发生的事情,包括滚动、调整大小等。