首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >异步代码检查

异步代码检查
EN

Code Review用户
提问于 2015-05-15 04:48:30
回答 3查看 182关注 0票数 8

Rubber鸭代码检查可能需要很长时间才能完成,所以我决定在后台线程上运行它们,而不是锁定IDE。唯一的缺点是。当检查运行时,IDE不会被锁定,因此用户最终可能会得到与陈旧的解析结果相反的检查结果--这可能意味着陈旧的检查结果(例如,您将Option Explicit添加到没有它的模块中),或者陈旧的令牌定位(例如添加或删除代码行;每个引用位于下面的令牌的检查结果在导航时会被错误地放置)。

但UX是值得的。我认为。

状态标签指示解析发生时..。

当检查正在运行时,...and:

所有结果都同时在网格中填充:

当没有发现任何问题时,就会有一种明显的状态:

这是演示者的代码:

代码语言:javascript
复制
namespace Rubberduck.UI.CodeInspections
{
    public class CodeInspectionsDockablePresenter : DockablePresenterBase
    {
        private CodeInspectionsWindow Control { get { return UserControl as CodeInspectionsWindow; } }

        private IEnumerable<VBProjectParseResult> _parseResults;
        private IList<ICodeInspectionResult> _results;
        private readonly IInspector _inspector;

        public CodeInspectionsDockablePresenter(IInspector inspector, VBE vbe, AddIn addin, CodeInspectionsWindow window)
            :base(vbe, addin, window)
        {
            _inspector = inspector;
            _inspector.IssuesFound += OnIssuesFound;
            _inspector.Reset += OnReset;
            _inspector.Parsing += OnParsing;
            _inspector.ParseCompleted += OnParseCompleted;

            Control.RefreshCodeInspections += OnRefreshCodeInspections;
            Control.NavigateCodeIssue += OnNavigateCodeIssue;
            Control.QuickFix += OnQuickFix;
            Control.CopyResults += OnCopyResultsToClipboard;
        }

        // indicates that the _parseResults are no longer in sync with the UI
        private bool _needsResync;

        private void OnParseCompleted(object sender, ParseCompletedEventArgs e)
        {
            ToggleParsingStatus(false);
            if (sender == this)
            {
                _needsResync = false;
                _parseResults = e.ParseResults;
                Task.Run(() => RefreshAsync());
            }
            else
            {
                _parseResults = e.ParseResults;
                _needsResync = true;
            }
        }

        private void OnParsing(object sender, EventArgs e)
        {
            ToggleParsingStatus();
            Control.Invoke((MethodInvoker) delegate
            {
                Control.EnableRefresh(false);
            });
        }

        private void ToggleParsingStatus(bool isParsing = true)
        {
            Control.Invoke((MethodInvoker) delegate
            {
                Control.ToggleParsingStatus(isParsing);
            });
        }

        private void OnCopyResultsToClipboard(object sender, EventArgs e)
        {
            var results = string.Join("\n", _results.Select(FormatResultForClipboard));
            var text = string.Format("Rubberduck Code Inspections - {0}\n{1} issue" + (_results.Count != 1 ? "s" : string.Empty) + " found.\n",
                            DateTime.Now, _results.Count) + results;

            Clipboard.SetText(text);
        }

        private string FormatResultForClipboard(ICodeInspectionResult result)
        {
            var module = result.QualifiedSelection.QualifiedName;
            return string.Format(
                "{0}: {1} - {2}.{3}, line {4}",
                result.Severity,
                result.Name,
                module.Project.Name,
                module.Component.Name,
                result.QualifiedSelection.Selection.StartLine);
        }

        private int _issues;
        private void OnIssuesFound(object sender, InspectorIssuesFoundEventArg e)
        {
            Interlocked.Add(ref _issues, e.Issues.Count);
            Control.Invoke((MethodInvoker) delegate
            {
                var newCount = _issues;
                Control.SetIssuesStatus(newCount);
            });
        }

        private void OnQuickFix(object sender, QuickFixEventArgs e)
        {
            e.QuickFix(VBE);
            _needsResync = true;
            OnRefreshCodeInspections(null, EventArgs.Empty);
        }

        public override void Show()
        {
            base.Show();
            Task.Run(() => RefreshAsync());
        }

        private void OnNavigateCodeIssue(object sender, NavigateCodeEventArgs e)
        {
            try
            {
                e.QualifiedName.Component.CodeModule.CodePane.SetSelection(e.Selection);
            }
            catch (COMException)
            {
                // gulp
            }
        }

        private void OnRefreshCodeInspections(object sender, EventArgs e)
        {
            Task.Run(() => RefreshAsync()).ContinueWith(t =>
            {
                Control.SetIssuesStatus(_results.Count, true);
            });
        }

        private async Task RefreshAsync()
        {
            Control.Invoke((MethodInvoker) delegate
            {
                Control.EnableRefresh(false);
                Control.Cursor = Cursors.WaitCursor;
            });

            try
            {
                if (VBE != null)
                {
                    if (_parseResults == null || _needsResync)
                    {
                        _inspector.Parse(VBE, this);
                        return;
                    }

                    var parseResults = _parseResults.SingleOrDefault(p => p.Project == VBE.ActiveVBProject);
                    if (parseResults == null || _needsResync)
                    {
                        _inspector.Parse(VBE, this);
                        return;
                    }

                    _results = await _inspector.FindIssuesAsync(parseResults);

                    Control.Invoke((MethodInvoker) delegate
                    {
                        Control.SetContent(_results.Select(item => new CodeInspectionResultGridViewItem(item))
                            .OrderBy(item => item.Component)
                            .ThenBy(item => item.Line));
                    });
                }
            }
            catch (COMException exception)
            {
                // swallow
            }
            finally
            {
                Control.Invoke((MethodInvoker) delegate
                {
                    Control.Cursor = Cursors.Default;
                    Control.SetIssuesStatus(_issues, true);
                    Control.EnableRefresh();
                });
            }
        }

        private void OnReset(object sender, EventArgs e)
        {
            _issues = 0;
            Control.Invoke((MethodInvoker) delegate
            {
                Control.SetIssuesStatus(_issues);
                Control.InspectionResults.Clear();
            });
        }
    }
}

下面是窗口的代码隐藏:

代码语言:javascript
复制
namespace Rubberduck.UI.CodeInspections
{
    public partial class CodeInspectionsWindow : UserControl, IDockableUserControl
    {
        private const string ClassId = "D3B2A683-9856-4246-BDC8-6B0795DC875B";
        string IDockableUserControl.ClassId { get { return ClassId; } }
        string IDockableUserControl.Caption { get { return "Code Inspections"; } }

        public BindingList<CodeInspectionResultGridViewItem> InspectionResults 
        {
            get { return CodeIssuesGridView.DataSource as BindingList<CodeInspectionResultGridViewItem>; }
            set { CodeIssuesGridView.DataSource = value; }
        }

        public CodeInspectionsWindow()
        {
            InitializeComponent();
            RefreshButton.Click += RefreshButtonClicked;
            QuickFixButton.ButtonClick += QuickFixButton_Click;
            GoButton.Click += GoButton_Click;
            PreviousButton.Click += PreviousButton_Click;
            NextButton.Click += NextButton_Click;
            CopyButton.Click += CopyButton_Click;

            var items = new List<CodeInspectionResultGridViewItem>();
            CodeIssuesGridView.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
            CodeIssuesGridView.DataSource = new BindingList<CodeInspectionResultGridViewItem>(items);

            CodeIssuesGridView.AutoResizeColumns();
            CodeIssuesGridView.Columns["Issue"].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;

            CodeIssuesGridView.SelectionChanged += CodeIssuesGridView_SelectionChanged;
            CodeIssuesGridView.CellDoubleClick += CodeIssuesGridView_CellDoubleClick;
        }

        public void ToggleParsingStatus(bool enabled = true)
        {
            StatusLabel.Image = enabled
                ? Resources.hourglass
                : Resources.exclamation_diamond;
            StatusLabel.Text = enabled
                ? RubberduckUI.Parsing
                : RubberduckUI.CodeInspections_Inspecting;
        }

        public void SetIssuesStatus(int issueCount, bool completed = false)
        {
            _issueCount = issueCount;
            if (issueCount == 0)
            {
                if (completed)
                {
                    StatusLabel.Image = Resources.tick_circle;
                    StatusLabel.Text = RubberduckUI.CodeInspections_NoIssues;
                }
                else
                {
                    StatusLabel.Image = Resources.hourglass;
                    StatusLabel.Text = RubberduckUI.CodeInspections_Inspecting;
                }
            }
            else
            {
                if (completed)
                {
                    StatusLabel.Image = Resources.exclamation_diamond;
                    StatusLabel.Text = string.Format("{0} issue" + (issueCount != 1 ? "s" : string.Empty), issueCount);
                }
                else
                {
                    StatusLabel.Image = Resources.hourglass;
                    StatusLabel.Text = string.Format("{0} ({1} issue" + (issueCount != 1 ? "s" : string.Empty) + ")", RubberduckUI.CodeInspections_Inspecting, issueCount);
                }
            }
        }

        private int _issueCount;
        public void EnableRefresh(bool enabled = true)
        {
            RefreshButton.Enabled = enabled;
            QuickFixButton.Enabled = enabled && _issueCount > 0;
        }

        public event EventHandler CopyResults;
        private void CopyButton_Click(object sender, EventArgs e)
        {
            var handler = CopyResults;
            if (handler != null)
            {
                handler(this, EventArgs.Empty);
            }
        }

        private void QuickFixButton_Click(object sender, EventArgs e)
        {
            QuickFixItemClick(QuickFixButton.DropDownItems.Cast<ToolStripMenuItem>().First(item => item.Checked), EventArgs.Empty);
        }

        private void PreviousButton_Click(object sender, EventArgs e)
        {
            var previousIssueIndex = (CodeIssuesGridView.SelectedRows[0].Index == 0)
                ? CodeIssuesGridView.Rows.Count - 1
                : CodeIssuesGridView.SelectedRows[0].Index - 1;

            CodeIssuesGridView.Rows[previousIssueIndex].Selected = true;
            var item = CodeIssuesGridView.Rows[previousIssueIndex].DataBoundItem as CodeInspectionResultGridViewItem;
            OnNavigateCodeIssue(item);
        }

        private void NextButton_Click(object sender, EventArgs e)
        {
            if (CodeIssuesGridView.Rows.Count == 0)
            {
                return;
            }

            var nextIssueIndex = (CodeIssuesGridView.SelectedRows[0].Index == CodeIssuesGridView.Rows.Count - 1)
                ? 0
                : CodeIssuesGridView.SelectedRows[0].Index + 1;

            CodeIssuesGridView.Rows[nextIssueIndex].Selected = true;
            var item = CodeIssuesGridView.Rows[nextIssueIndex].DataBoundItem as CodeInspectionResultGridViewItem;
            OnNavigateCodeIssue(item);
        }

        private IDictionary<string, Action<VBE>> _availableQuickFixes;
        private void CodeIssuesGridView_SelectionChanged(object sender, EventArgs e)
        {
            var enableNavigation = (CodeIssuesGridView.SelectedRows.Count != 0);
            NextButton.Enabled = enableNavigation;
            PreviousButton.Enabled = enableNavigation;
            GoButton.Enabled = enableNavigation;
            CopyButton.Enabled = enableNavigation;

            var quickFixMenu = QuickFixButton.DropDownItems;
            if (quickFixMenu.Count > 0)
            {
                foreach (var quickFixButton in quickFixMenu.Cast<ToolStripMenuItem>())
                {
                    quickFixButton.Click -= QuickFixItemClick;
                }
            }

            if (CodeIssuesGridView.SelectedRows.Count > 0)
            {
                var issue = (CodeInspectionResultGridViewItem) CodeIssuesGridView.SelectedRows[0].DataBoundItem;
                _availableQuickFixes = issue.GetInspectionResultItem()
                    .GetQuickFixes();
                var descriptions = _availableQuickFixes.Keys.ToList();

                quickFixMenu.Clear();
                foreach (var caption in descriptions)
                {
                    var item = (ToolStripMenuItem) quickFixMenu.Add(caption);
                    if (quickFixMenu.Count > 0)
                    {
                        item.CheckOnClick = false;
                        item.Checked = quickFixMenu.Count == 1;
                        item.Click += QuickFixItemClick;
                    }
                }
            }

            QuickFixButton.Enabled = QuickFixButton.HasDropDownItems;
        }

        public event EventHandler<QuickFixEventArgs> QuickFix;
        private void QuickFixItemClick(object sender, EventArgs e)
        {
            var quickFixButton = (ToolStripMenuItem)sender;
            if (QuickFix == null)
            {
                return;
            }

            var args = new QuickFixEventArgs(_availableQuickFixes[quickFixButton.Text]);
            QuickFix(this, args);
        }

        public void SetContent(IEnumerable<CodeInspectionResultGridViewItem> inspectionResults)
        {
            var results = inspectionResults.ToList();

            CodeIssuesGridView.DataSource = new BindingList<CodeInspectionResultGridViewItem>(results);
            CodeIssuesGridView.Refresh();
        }

        private void GoButton_Click(object sender, EventArgs e)
        {
            var issue = CodeIssuesGridView.SelectedRows[0].DataBoundItem as CodeInspectionResultGridViewItem;
            OnNavigateCodeIssue(issue);
        }

        public event EventHandler<NavigateCodeEventArgs> NavigateCodeIssue;
        private void CodeIssuesGridView_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
        {
            if (e.RowIndex < 0)
            {
                return;
            }
            var issue = CodeIssuesGridView.Rows[e.RowIndex].DataBoundItem as CodeInspectionResultGridViewItem;
            OnNavigateCodeIssue(issue);
        }

        private void OnNavigateCodeIssue(CodeInspectionResultGridViewItem item)
        {
            var handler = NavigateCodeIssue;
            if (handler == null)
            {
                return;
            }

            var result = item.GetInspectionResultItem();
            handler(this, new NavigateCodeEventArgs(result.QualifiedSelection));
        }

        public event EventHandler RefreshCodeInspections;
        private void RefreshButtonClicked(object sender, EventArgs e)
        {
            var handler = RefreshCodeInspections;
            if (handler == null)
            {
                return;
            }

            toolStrip1.Refresh();

            handler(this, EventArgs.Empty);
        }
    }
}

这东西很管用..。但是肯定有一些问题-- RefreshAsync运行的频率比它应该的要高。

EN

回答 3

Code Review用户

回答已采纳

发布于 2015-05-15 19:01:10

一开始,您的事件处理程序命名使我感到困惑。标准的命名约定是OnEventName引发事件。标准处理程序看起来像ObjectName_EventName

代码语言:javascript
复制
public class ClassWithEvent : IDisposable
{
    public event EventHandler SomeEvent;
    protected virtual void OnSomeEvent()
    {
        var e = SomeEvent;
        if (e != null)
            e(this, EventArgs.Empty);
    }

    public void Dispose()
    {
        SomeEvent = null;
    }
}

public class ClassListensToEvent
{
    private ClassWithEvent myClass;

    public ClassListensToEvent()
    {
        myClass.SomeEvent += myClass_SomeEvent;
    }

    private void myClass_SomeEvent(object sender, EventArgs eventArgs)
    {
    }
}

我会对RefreshAsync进行一些重大的重构,以及它是如何使用的。我不完全理解您的工作流程,但我希望RefreshAsync可能会被取消。没有意义的刷新和过时的结果。我将使用CancellationTokenSource并将CancellationToken传递给RefreshAsyncTask.Run,如下所示:

代码语言:javascript
复制
Task.Run(() => RefreshAsync(token), token);

如果您得到了更新的结果,首先要做的就是调用CancellationTokenSource.Cancel()来停止任何现有的刷新。在RefreshAsync中,您可以优雅地捕捉和吞咽TaskCancelledException

最后,RefreshAsync不是很异步。您多次使用Invoke,这会阻塞UI线程。理想情况下,RefreshAsync将是异步的,直到完成,然后在结束时(通过BeginInvoke)更新一次GUI。

通过这些更改,您可以取消RefreshAsync,这样就不会在旧数据上浪费周期,也不会在GUI上绘制旧的结果。

票数 4
EN

Code Review用户

发布于 2015-05-15 06:43:03

只是几枪..。

以减少这里的小代码重复

私有void OnParseCompleted(对象发送方,ParseCompletedEventArgs e) { ToggleParsingStatus(false);if (发送方== this) { _needsResync = false;_parseResults = e.ParseResults;Task.Run(() => RefreshAsync());}=={ _parseResults = e.ParseResults;_needsResync = true;}}

你可以把这个重构成

代码语言:javascript
复制
private void OnParseCompleted(object sender, ParseCompletedEventArgs e)
{
    ToggleParsingStatus(false);
    _parseResults = e.ParseResults;
    _needsResync = sender != this
    if (!needsResync)
    {
        Task.Run(() => RefreshAsync());
    }
}

private async Task RefreshAsync()方法中,您有更多的“问题”。首先,您应该通过在if (VBE == null)上使用保护子句来减少水平间距,并尽早返回。

下一个问题在这里

parseResults = _parseResults.SingleOrDefault(p => p.Project == VBE.ActiveVBProject);

你能看见吗??对SingleOrDefault()的调用将基于名称返回单个项或默认项。一个项目!=复数!因此,将局部变量重命名为parseResult

仔细看看这两条if语句

if (\_parseResults == null || \_needsResync) { \_inspector.Parse(VBE, this); return; } var parseResults = \_parseResults.SingleOrDefault(p => p.Project == VBE.ActiveVBProject); if (parseResults == null || \_needsResync) { \_inspector.Parse(VBE, this); return; }

我们看到,在第二个if条件下,我们可以省略|| _needsResync,因为这永远不会是true

我刚看到了你写十字句的好方法。

票数 6
EN

Code Review用户

发布于 2015-05-15 07:36:30

小小的评论,我会像这样重构OnCopyResultsToClipboard:

代码语言:javascript
复制
    private void OnCopyResultsToClipboard(object sender, EventArgs e)
    {
        var results = string.Join(Environment.NewLine, _results.Select(FormatResultForClipboard));
        var plural = _results.Count != 1 ? "s" : string.Empty;
        var text = string.Format("Rubberduck Code Inspections - {0}{3}{1} issue{2} found.{3} {4}",
                        DateTime.Now, _results.Count, plural, Environment.NewLine, results);

        Clipboard.SetText(text);
    }

你在字符串格式的同时使用字符串连接,这让我觉得很奇怪。

票数 4
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/90798

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档