当异步任务使用户体验不佳时


9

我正在编写一个COM插件,该插件扩展了迫切需要它的IDE。这里涉及许多功能,但是为了这篇文章,我们将其缩小到2:

  • 有一个“代码资源管理器”工具窗口,其中显示一个树状视图,允许用户浏览模块及其成员。
  • 有一个“代码检查”工具窗口,其中显示一个datagridview,可让用户浏览代码问题并自动修复它们。

两种工具都有一个“刷新”按钮,用于启动异步任务,该任务将解析​​所有打开的项目中的所有代码。该代码浏览器使用的解析结果建立树形视图代码评审使用的解析结果,以找出代码问题并显示其结果的datagridview

我在这里想要做的是在功能之间共享解析结果,以便在代码浏览器刷新时,代码检查会知道它并可以刷新自身,而无需重做代码浏览器刚刚进行的解析工作。

因此,我做了什么,将解析器类设置为事件提供者,这些功能可以注册到:

    private void _parser_ParseCompleted(object sender, ParseCompletedEventArgs e)
    {
        Control.Invoke((MethodInvoker) delegate
        {
            Control.SolutionTree.Nodes.Clear();
            foreach (var result in e.ParseResults)
            {
                var node = new TreeNode(result.Project.Name);
                node.ImageKey = "Hourglass";
                node.SelectedImageKey = node.ImageKey;

                AddProjectNodes(result, node);
                Control.SolutionTree.Nodes.Add(node);
            }
            Control.EnableRefresh();
        });
    }

    private void _parser_ParseStarted(object sender, ParseStartedEventArgs e)
    {
        Control.Invoke((MethodInvoker) delegate
        {
            Control.EnableRefresh(false);
            Control.SolutionTree.Nodes.Clear();
            foreach (var name in e.ProjectNames)
            {
                var node = new TreeNode(name + " (parsing...)");
                node.ImageKey = "Hourglass";
                node.SelectedImageKey = node.ImageKey;

                Control.SolutionTree.Nodes.Add(node);
            }
        });
    }

而且有效。我遇到的问题是...有效-我的意思是,当代码检查刷新时,解析器告诉代码浏览器(和其他所有人)“老兄,有人在解析,您想为此做些什么? ” -解析完成后,解析器告诉其侦听器“是的,我有新的解析结果供您使用,您想为此做些什么?”。

让我通过一个例子来说明这个问题:

  • 用户打开代码浏览器,告诉用户“等等,我在这里工作”;用户继续在IDE中工作,代码资源管理器重绘自身,生活很美好。
  • 然后,用户调出代码检查,告诉用户“等等,我在这里工作”;解析器告诉代码浏览器“伙计,有人在解析,您想对此做些什么?” -代码浏览器告诉用户“等等,我在这里工作”;用户仍然可以在IDE中工作,但由于它令人耳目一新,因此无法浏览代码浏览器。而且他也在等待代码检查完成。
  • 用户在要解决的检查结果中看到代码问题;他们双击导航到它,确认代码存在问题,然后单击“修复”按钮。该模块已修改,需要重新解析,因此代码检查将继续进行;代码浏览器告诉用户“等等,我在这里工作”,...

看到这是怎么回事?我不喜欢它,我敢打赌用户也不喜欢它。我想念什么?我应该如何在要素之间共享解析结果,而又让用户控制何时应该执行要素?

我问的原因是因为我认为,如果我将实际工作推迟到用户积极决定刷新之前,并且将解析结果“缓存”进来……那么我将刷新树形视图,在可能过时的解析结果中定位代码问题……这实际上使我回到了第一个问题,每个功能都具有自己的解析结果:我可以通过任何方式在功能之间共享解析结果拥有漂亮的UX吗?

代码是,但是我不是在寻找代码,而是在寻找概念


2
只是一个FYI,我们也有一个UserExperience.SE网站。我认为这是热门话题,因为它讨论的是代码设计而不是用户界面,但是我想让您知道,以防您的更改更多地移向UI方面而不是问题的代码/设计方面。

解析时,这是全有还是全无的操作?例如:文件中的更改会触发完全重新解析,还是仅针对该文件以及依赖于该文件的文件?
摩根(Morgen)

@Morgen有两件事:VBAParser由ANTLR生成,并为我提供了一个解析树,但是这些功能不会消耗它。该RubberduckParser解析树,走它,并发出VBProjectParseResult一个包含Declaration有对象所有的References解决- 这是什么功能需要输入..所以是的,这几乎是一个全有或全无的情况。该RubberduckParser是足够聪明,给没有被修改虽然不是重新解析模块。但是,如果存在瓶颈,则不是解析,而是代码检查。
Mathieu Guindon

4
我想,我会这样做:当用户触发刷新时,该工具窗口触发解析并显示其正在工作。其他工具窗口尚未通知,它们仍显示旧信息。直到解析器完成。届时,解析器将向所有工具窗口发出信号,以使用新信息刷新其视图。如果用户在解析器运行时转到另一个工具窗口,则该窗口还将进入“正在运行...”状态并发出重新解析的信号。然后,解析器将重新开始,以将最新信息同时传递到所有窗口。
cmaster-恢复莫妮卡

2
@cmaster我也赞成将该评论作为答案。
RubberDuck

Answers:


7

我可能会采用的方法是减少关注于提供完美的结果,而关注于尽力而为的方法。这至少会导致以下更改:

  • 将当前启动重新解析的逻辑转换为请求逻辑,而不是初始化逻辑。

    请求重新解析的逻辑可能最终看起来像这样:

    IF parseIsRunning IS false
      startParsingThread()
    ELSE
      SET shouldParse TO true
    END
    

    这将与包装解析器的逻辑配对,看起来可能像这样:

    SET parseIsRunning TO true
    DO 
      SET shouldParse TO false
      doParsing()
    WHILE shouldParse IS true
    SET parseIsRunning TO false
    

    重要的是,解析器将一直运行到满足最近的重新解析请求为止,但在给定的时间运行的解析器不超过一个。

  • 删除ParseStarted回调。现在,请求重新解析是一劳永逸的操作。

    或者,将其转换为除了在不妨碍用户交互的GUI的某些方式中显示刷新指示器之外,什么也不做。

  • 尝试为过时的结果提供最少的处理。

    对于Code Explorer,这可能很简单,例如为用户想要导航的方法查找合理数量的上下一行,或者如果找不到确切的名称,则查找最近的方法。

    我不确定什么适合代码检查器。

我不确定实现细节,但是总的来说,这与NetBeans编辑器处理此行为的方式非常相似。总是非常迅速地指出它当前正在刷新,但是也不会阻止对该功能的访问。

过时的结果通常足够好-尤其是与没有结果相比时。


1
很好,但是我有一个问题:我ParseStarted用来禁用[刷新]按钮(Control.EnableRefresh(false))。如果我删除了该回调,并让用户单击它,那么我将处于两种同时执行的任务进行解析的情况下……如何避免出现这种情况而又不禁用某人对其他所有功能的刷新正在解析?
Mathieu Guindon 2015年

@ Mat'sMug我更新了我的答案,以包括问题的那个方面。
摩根(Morgen)

我同意这种方法,只是我仍然保留一个ParseStarted事件,以防您确实希望允许UI(或其他组件)有时警告用户正在进行重新解析。当然,您可能希望文档记录调用者应尝试阻止用户使用(即将成为)过时的当前解析结果。
马克·赫德
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.