我正在制作Visual Studio装饰扩展程序。如果没有用户输入至少2秒,我想更新装饰品。所以我构建了一个工人,并尝试删除和添加装饰,但VS说它无法更新,因为非ui线程已经调用它。所以我没有线程等待,然后我的编辑器变得非常迟缓(因为ui线程等待)
我想知道是否有办法使用延迟更新来更新装饰品。 绘图装饰是通过调用AddAdornment()完成的,我找不到如何调用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;
}
答案 0 :(得分:4)
我不确定你为什么会被投票,特别是因为在处理扩展时这是一个有趣的问题。
因此,对于您的第一个问题:Visual Studio与WPF具有相同的要求(由于其COM依赖性而增加了一些复杂性)。当您不在主(UI)线程上时,您无法更新UI元素。不幸的是,如果你直接潜入并使用你用于WPF的策略来接近它,你将会经历另一个问题世界(主要是死锁)。
首先,了解如何在Visual Studio扩展域中处理从后台切换到UI线程的问题。我发现Asynchronous and Multithreaded programming within VS using JoinableTaskFactory有助于解释。
我必须通过昂贵的解析操作做类似的事情。这很直接。
我的解析器作为IViewModelTagger
实例的一部分执行,并使用以下序列(粗略地):
ITextBuffer.ChangedLowPriority
事件处理程序订阅async void
事件。CancellationToken.Cancel()
电话取消正在进行的任何解析操作。取消令牌将传递到支持它的所有内容中(在Roslyn中,它可以在任何您希望的地方得到支持)。 Task.Delay(200, m_cancellationToken)
调用。根据我的打字速度和Roslyn的操作CancellationToken
重载任何昂贵的东西(我的解析工作也非常轻量级),我200ms。 YMMV。我使用需要UI线程的WPF组件,并且它们在IViewModelTagger
和IWpfTextViewListener
内混合在一起。它们的重量足够轻,我可以跳过它们的异步,但是在非常大的类中它们可以挂起UI。
为了解决这个问题,我做了以下几点:
async void
事件处理程序。Task.Run()
首先进行昂贵的操作,防止UI被阻止。await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync()
来获取UI线程。我提到过"其他SDK操作",这很重要。 SDK中除了主线程之外的任何事情都有一些你不能做的事情(内存现在让我失望,但特别是TextView的某些部分会失败,如果它们在后台线程上被访问则不一致)。 / p>
有更多选项可以执行UI线程的工作(普通Task.Run
工作,以及ThreadHelper.JoinableTaskFactory.Run
)。在我的回答中早先链接的Andrew Arnott帖子解释了所有的选择。您将完全理解这一点,因为根据任务的不同,有理由使用其他语言。
希望有所帮助!
答案 1 :(得分:0)
Task.Delay
返回一个在您延迟时完成的任务。如果你这样称它并忽略结果,它就没有做你想象的那样。你可能想要的是,而不是像你一样调用Task.Factory.StartNew,你想要:
var cancellationTokenSource = new CancellationTokenSource();
Task.Delay(2000, cancellationTokenSource.Token).ContinueWith(() => DoWork(), cancellationTokenSource.Token, TaskScheduler.Current).
这说明有效地启动了一个等待2秒的计时器,然后一旦完成就在UI线程上运行DoWork方法。如果发生更多的输入,那么你可以调用cancellationTokenSource.Cancel()并再次运行。
另外,我不得不问你的类型" CSharpRegionParser"。如果您需要区域信息并且您在Visual Studio 2015上,那么您可以从Roslyn获取语法树,您应该观察工作区更改事件而不是挂钩LayoutChanged。您最好将系统构建为标记器/装饰管理器对,因为编写它可能更清楚......我不清楚为什么要在LayoutChanged中解析逻辑LayoutChanged是在视觉布局期间发生的事情,包括滚动,调整大小等。