处置表单上的C#异步回调

时间:2010-06-14 19:55:09

标签: c# winforms exception-handling asynchronous callback

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

守则:

  public void RequestUserPhoto(int userID)
        {
            WCF.Service.BeginGetUserPhoto(userID,
                new AsyncCallback(GetUserPhotoCB), userID);
        }

    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或后台工作者,或互斥,或其他什么,只是为了它不会轰炸我的应用程序,这将带来很大的错误空间。我需要的是一种干净的方式来异步调用服务,然后在表单恰好关闭时将其转换为单向调用。换句话说,让服务完成运行,但我不关心结果是什么,并且不调用回调,因为它不再存在。

5 个答案:

答案 0 :(得分:1)

public void UpdateUserPhoto(int userID, Binary photo)
{
  if ( Disposed || !IsHandleCreated )
    return;
  if (InvokeRequired)
  ...

答案 1 :(得分:0)

您正在为回调提供一个地址,以便在操作完成时点击。当您关闭表单时,该地址无效,因此您收到此错误。在允许表单关闭之前,您需要建立一种确定您是否有未完成电话的方式。

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

在异步通话仍处于活动状态时,您不应该让表单关闭。使用BackgroundWorker,您可以轻松确定异步通话是否处于活动状态。

答案 2 :(得分:0)

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

答案 3 :(得分:0)

创建一个标志,类似“IsWaiting”作为ManualResetEvent(甚至是一个简单的bool),将其设置为true,并在异步结果返回时将其设置为false。

在你的类dispose方法中检查标志,只有在标志清除后才处理对象。 (暂停,以防万一有错误)

答案 4 :(得分:0)

我对这个问题有很多好的评论,但是我在整个申请过程中都没有好的模式。我在这里发布了我最终做的答案,并希望它会帮助其他人。

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

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

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