首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >VSTO异步/等待-无法取消长时间运行的操作

VSTO异步/等待-无法取消长时间运行的操作
EN

Stack Overflow用户
提问于 2022-07-11 16:41:25
回答 3查看 78关注 0票数 0

我正在用VSTO和WPF (MVVM)在C#中开发一个Word搜索工具。

我使用Microsoft.Office.Interop.Word.Find()方法并遍历文档来查找匹配项。我需要处理的一些文档超过30万个字符,因此搜索需要花费10+秒。我想给我的用户取消操作的选项。

我面临的问题是,要取消长时间运行的操作,按钮是不可访问的,因为UI/主线程由于Find操作而一直处于忙碌状态,触发回主线程的编组--中继命令从未被触发。我的数据绑定是正确的,并且使用了一个不使用UI/主线程的长时间运行操作来测试按钮。

代码语言:javascript
复制
public class SmartFindViewModel : BindableBase
{
   ctor()
   {
            FindCommand = new RelayCommand(o => Find(), o => CanFindExecute());
   }

   private async void Find()
   {
            var token = cancellationTokenSource.Token;
            **Update user here and show progress view**
            
            try
            {
                await System.Threading.Tasks.Task.Run(async() => { 
                        var searchResults = await SearchRange(token);
                        System.Windows.Application.Current.Dispatcher.Invoke(() =>
                        {
                            **Update results on UI Thread**
                        });
                        return;
                    }
                });
            }
            catch (OperationCanceledException)
            {
                ...
            }
            catch(Exception ex)
            {
                ...
            }
            finally
            {
                **Hide progress view**
            }
            
    }

    public async Task<List<SmartFindResultViewModel>> SearchRange(CancellationToken cancellationToken)
    {
            ** Get Word range**
            await System.Threading.Tasks.Task.Run(() =>
            {
                do
                {
                    range.Find.Execute();
                    if (!range.Find.Found) return;
                    **
                } while (range.Find.Found && !cancellationToken.IsCancellationRequested);
            });

            return Results;

    }
}

我的问题很简单,如果UI线程通过互操作方法保持忙碌,那么如何允许按钮保持操作呢?或者仅仅是VSTO的一个限制或者我的代码有什么问题?

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2022-07-11 23:47:38

如果用户界面线程被互操作方法占用,那么

如何允许按钮保持操作呢?

简短的回答:你不能。如果UI线程一直忙于做大量的UI更新,那么它也不能正确地响应。

唯一真正的答案是不要中断UI线程。我会考虑批次更新,而不是应用更新比,比方说,每100毫秒。我有一个ObservableProgress,它可以在时间方面有所帮助。

票数 1
EN

Stack Overflow用户

发布于 2022-07-11 17:07:35

每当您在主线程上运行代码时,请确保线程正在释放Windows,await操作符依赖它。但真正的解决方案是避免在二级线程上使用Word对象。

代码语言:javascript
复制
        public static void DoEvents(bool OnlyOnce = false)
        {
            MSG msg;
            while (PeekMessage(out msg, IntPtr.Zero, 0, 0, 1/*PM_REMOVE*/))
            {
                TranslateMessage(ref msg);
                DispatchMessage(ref msg);
                if (OnlyOnce) break;
            }
        }

       [StructLayout(LayoutKind.Sequential)]
        public struct POINT
        {
            public int X;
            public int Y;
            public POINT(int x, int y)
            {
                this.X = x;
                this.Y = y;
            }
            public static implicit operator System.Drawing.Point(POINT p)
            {
                return new System.Drawing.Point(p.X, p.Y);
            }
            public static implicit operator POINT(System.Drawing.Point p)
            {
                return new POINT(p.X, p.Y);
            }
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct MSG
        {
            public IntPtr hwnd;
            public uint message;
            public UIntPtr wParam;
            public IntPtr lParam;
            public int time;
            public POINT pt;
            public int lPrivate;
        }
        [DllImport("user32.dll")]
        static extern sbyte GetMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax);
        [DllImport("user32.dll")]
        static extern bool TranslateMessage([In] ref MSG lpMsg);
        [DllImport("user32.dll")]
        static extern IntPtr DispatchMessage([In] ref MSG lpmsg);
        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool PeekMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax, uint wRemoveMsg);
票数 1
EN

Stack Overflow用户

发布于 2022-07-14 18:08:28

基于这里在堆栈溢出上的几个帖子,我构建的解决方案是将System.Threading.Tasks.Task.Run与在主线程中打开进度表单结合起来,并让正在运行的线程响应当用户按下(非阻塞的)进度表单时触发的任务取消。

它的工作如下:

在主线程中启动长期运行的任务,如下所示:

代码语言:javascript
复制
ThreadWriterTaskWithCancel(AddressOf _LoadData)

ThreadWriterTaskWithCancel如下:

代码语言:javascript
复制
Protected Function ThreadWriterTaskWithCancel(mytask As TaskDelegateWithCancel) As Boolean

    userCancelled = False
    Dim ts As New CancellationTokenSource
    Dim tsk As Task(Of Boolean) = Nothing
    Try
        tsk =
            System.Threading.Tasks.Task.Run(Of Boolean)(
                Function()
                    'Thread.CurrentThread.Priority = ThreadPriority.Highest
                    ' Run lenghty task
                    Dim userCancelled As Boolean = mytask(ts.Token)

                    ' Close form once done (on GUI thread)
                    If progressBarFrm.Visible Then progressBarFrm.Invoke(New System.Action(Sub() progressBarFrm.Close()))

                    Return userCancelled
                End Function, ts.Token)

        ' Show the form - pass the task cancellation token source that is also passed to the long-running task
        progressBarFrm.ShowForm(ts, True)

    Catch ex As Exception
        WorkerErrorLog.AddLog(ex, Err)
    Finally
        ts.Dispose()
    End Try

在进度条中,这是在单击X时触发的代码(我不完全理解这一点,但它是工作的:- ):

代码语言:javascript
复制
    Private Sub UserFormProgressBar_FormClosing(sender As Object, e As FormClosingEventArgs) Handles MyBase.FormClosing
    ' only cancel the long running task when the closing is triggered by the user pressing the X
    Dim closedByUser As Boolean = Not (New StackTrace().GetFrames().Any(Function(x) x.GetMethod().Name = "Close"))
    If closedByUser Then
        If _ts IsNot Nothing Then
            _ts.Cancel()
        Else
            e.Cancel = True
        End If
    End If
End Sub

_ts是通过线程函数传入的任务取消令牌源。

_ts.cancel将触发任务取消请求。让长期运行的任务在它处理的文档的循环中监视它:

代码语言:javascript
复制
        For Each file As Document In documentList
            Try
                ' do some processing
                ct.ThrowIfCancellationRequested()

            Catch ex As OperationCanceledException
                userCancelled = True
                Return userCancelled
            End Try

            fileCount += 1
            ProgressBar.BeginInvoke(Sub() ProgressBar.Value = pctProgress
        Next

由于长期运行的任务和进度表单之间的交互是在两个不同的线程中进行的,所以使用以下构造:

代码语言:javascript
复制
ProgressBar.BeginInvoke(Sub() ProgressBar.Value = pctProgress

请注意,如果添加线程任务,则可以锁定主线程:

代码语言:javascript
复制
        Application.System.Cursor = WdCursorType.wdCursorWait
        Application.ScreenUpdating = False

但如果不需要这样做,那就不用费心了。

这是非常稳定和可重复的,但是当线程运行在主线程(与UI相同的线程)时,线程操作要慢一些。大约一半的速度。我还没破解那个..。

在运行时,显示如下:

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

https://stackoverflow.com/questions/72941891

复制
相关文章

相似问题

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