【问题标题】:C# async callback on disposed form已处理表单上的 C# 异步回调
【发布时间】:2011-03-03 16:43:16
【问题描述】:

快速提问:我的 winform 应用程序 (c#) 中的一个表单对 WCF 服务进行异步调用以获取一些数据。如果表单恰好在回调发生之前关闭,它会因访问已处置对象的错误而崩溃。检查/处理这种情况的正确方法是什么?该错误发生在对更新表单的方法的 Invoke 调用中,但我无法深入了解内部异常,因为它表示代码已被优化。

代码:

公共无效RequestUserPhoto(int userID) { WCF.Service.BeginGetUserPhoto(用户 ID, 新的 AsyncCallback(GetUserPhotoCB),用户 ID); }
    public void GetUserPhotoCB(IAsyncResult result)
    {
        var photo = WCF.Service.EndGetUserPhoto(result);
        int userID = (int)result.AsyncState;
        UpdateUserPhoto(userID, photo);
    }

    public delegate void UpdateUserPhotoDelegate(int userID, Binary photo);
    public void UpdateUserPhoto(int userID, Binary photo)
    {
        if (InvokeRequired)
        {
            var d = new UpdateUserPhotoDelegate(UpdateUserPhoto);
            Invoke(d, new object[] { userID, photo });
        }
        else
        {
            if (photo != null)
            {
                var ms = new MemoryStream(photo.ToArray());
                var bmp = new System.Drawing.Bitmap(ms);
                if (userID == theForm.AuthUserID)
                {
                    pbMyPhoto.BackgroundImage = bmp;
                }
                else
                {
                    pbPhoto.BackgroundImage = bmp;
                }
            }
        }
    }

更新:

我仍然不知道该去哪里。我在这里真正需要的是一种设计模式,用于从 win 表单进行 WCF 异步服务调用,该模式在异步调用返回之前可以优雅地处理表单关闭。用户可以随时单击表单或任何表单上的 X。这个问题比我上面展示的单个例子要大得多。我的应用程序实际上进行了数百次 WCF 调用,我试图弄清楚如何优雅地处理这些,并且在整个应用程序中以一致的方式。例如,如果我必须为每个 WCF 调用添加 100 行带有 ManualResetEvents 或后台工作人员或互斥锁或其他任何东西的代码,这样它就不会轰炸我的应用程序,这将引入很大的错误空间。我需要的是一种干净的方式来异步调用服务,然后在表单关闭时将其转换为单向调用。也就是说,让服务跑完,但我不管结果是什么,也不要调用回调,因为它已经不存在了。

【问题讨论】:

  • 查看更新的代码。 Invoke 调用时发生崩溃(说代码已优化,不会提供任何细节)
  • 没有办法跟踪 ASYNC 调用并在 form_closure 事件中明确取消它吗?还是该线程永远卡在内存中,如果它永远不会返回?

标签: c# winforms exception-handling asynchronous callback


【解决方案1】:
public void UpdateUserPhoto(int userID, Binary photo)
{
  if ( Disposed || !IsHandleCreated )
    return;
  if (InvokeRequired)
  ...

【讨论】:

  • 我认为这不会发生。由于 WCF 回调仍然持有对表单的引用,我认为这足以防止它被收集。
【解决方案2】:

我相信 Form 对象上有一个 IsDisposed 属性。您可以在调用 Invoke 之前检查该属性。

【讨论】:

  • 试过了...调试器说 IsDisposed = false,但仍然崩溃说对象已被释放。
  • 嗯,我刚刚发现了这个问题 (stackoverflow.com/questions/1874728/…)。似乎与您的问题有关。
【解决方案3】:

创建一个标志,例如“IsWaiting”作为 ManualResetEvent(甚至是一个简单的布尔值),将其设置为 true,并且仅在异步结果返回时将其设置为 false。

在您的类 dispose 方法中检查标志,并且仅在标志清除后才释放对象。 (设置一个超时,以防万一出现错误)

【讨论】:

    【解决方案4】:

    当操作完成时,你给回调一个地址。关闭表单时地址无效,因此您收到此错误。在允许表单关闭之前,您需要确定是否有未完成的呼叫。

    我会研究BackgroundWorker class。我相信你仍然可以通过在回调触发之前关闭你的表单来设置崩溃,但是使用BackgroundWorker,你应该能够询问它的状态并更优雅地处理你的情况。

    您不应在 Async 调用仍处于活动状态时关闭表单。使用BackgroundWorker,您可以轻松确定 Async 调用是否处于活动状态。

    【讨论】:

    • 关于使用 ObjectDisposedException 的 try/catch 块包装 Invoke 调用并丢弃异常的想法?
    • Try-catch 无济于事。当调用完成时,异步调用引用了 GONE(表单已关闭)的内存地址。查看这篇关于在 Winforms 中使用 BackgroundWorker 的文章(注意如何取消):bit.ly/cBQUhF
    • 你是说我应该使用后台工作人员在我的应用程序中进行所有异步 wcf 调用吗?这是一个很好的模式吗?
    • 我只是建议使用 BackgroundWorker 会为您节省一些代码,因为 UI 线程同步已被视为取消挂起的操作。您的问题围绕在您的表单被处置时异步操作爆炸。对于完全不同的方式,您可以考虑 Ayende 的 MSDN 文章:msdn.microsoft.com/en-us/magazine/ff796225.aspx
    【解决方案5】:

    我在这个问题上获得了很多优秀的 cmets,但没有可以在整个应用程序中使用的良好模式。我在这里发布了我最终做的答案,希望它可以帮助其他人。

    自动生成的 WCF 代理创建同步调用方法、使用开始/结束模式的异步方法和使用 Completed 事件的基于事件的委托。我在上面的原始问题中发布的示例使用了 Begin/End 模式。问题是当回调发生时,你将不得不做一个 Invoke 来访问你的 UI 线程。如果该 UI 线程不再存在(即用户关闭了窗口),那么您就有问题了。新的基于事件的模式会自动找到返回 UI 线程的方式,并且根据我的测试,我无法通过在服务完成之前关闭来使其崩溃。我猜代理足够聪明,如果内存地址不存在,不会调用完成的处理程序?或者它可能挂在处理程序的内存地址上,所以它不会被垃圾收集?因此,您所要做的就是添加一个 Completed 事件处理程序,启动您的调用等 servicenameAsync(),然后等待它返回到您的处理程序(在 UI 线程上)。我还为了确保将完成的处理程序包装在 try/catch 块中以处理 ObjectDisposedExceptions(如果有)。

    现在最重要的是:它不会崩溃。

    一个问题(至少对我而言)...我使用单例模式来访问我的 WCF 服务。这样做的问题是它创建了对 WCF 代理的静态引用,在整个应用程序中使用。听起来很方便,但是当您在进行异步调用之前将事件处理程序附加到 Completed 事件时,这些事件可能会被重复、三次等。每次进行调用时,除了一个之外,您还会添加另一个已完成的事件处理程序已添加到您的静态 WCF 代理。为了解决这个问题,我开始为每个调用声明一个新的代理客户端,或者如果每个 winform 类没有进行多个调用,则每个 winform。如果有人对此有任何 cmets 或更好的方法,请告诉我!单例模式的最大问题是,如果您同时打开多个窗口(不同的类)调用相同的异步方法,但您希望它们返回不同的 Completed 处理程序,则不能这样做。

    【讨论】:

    • 关于答案的最后一部分(关于多个窗口使用同一个代理实例),您可以在调用异步方法时传递一个userState对象,该对象将在相应的EventArgs中返回完成的活动。这个 userState 对象可以用来区分调用相同方法的多个窗口,并识别哪个结果对应于特定的窗口实例。
    • 优秀的评论。主要问题是,我需要调用 2 个不同的已完成处理程序。一个调用应该返回到已完成的处理程序#1,而另一个表单的另一个调用需要调用已完成的处理程序#2。我看不到一个直接的方法来做到这一点。我必须设置某种类型的全局事件处理程序,它总是被调用,然后使用 userstate 对象来确定到底需要做什么。
    猜你喜欢
    • 1970-01-01
    • 2016-12-13
    • 2016-08-08
    • 2013-08-05
    • 1970-01-01
    • 2021-04-16
    • 2021-06-17
    • 2019-02-12
    • 1970-01-01
    相关资源
    最近更新 更多