أين يمكن اعتراض الاتصال الفاشل في فئة الاتصال WCF؟

StackOverflow https://stackoverflow.com/questions/1204534

  •  05-07-2019
  •  | 
  •  

سؤال

أحاول كتابة فصل دراسي يتضمن مكالمات WCF (العميل هو Silverlight، إذا كان الأمر مهمًا).كل شيء يعمل بسلاسة، لكنني لست متأكدًا من كيفية تعويض فشل الاتصال، كما لو أن الخادم لن يستجيب.يبدو أن جزءًا من العمل يحدث في مكان ما في الكود الناتج من ChannelFactory، لكنني لست متأكدًا.نرحب أيضًا بمراجعة الكود العام.:)

خلاصة القول، إن المحيط بإنشاء القناة، أو تفويض نتيجة البداية أو عدم المزامنة في محاولة/التقاط لا يعترض الاتصال الفاشل.أرغب في تشغيل هذا المصيد لحدث ServiceCallError.

public class ServiceCaller : IDisposable
{
    private IFeedService _channel;

    public ServiceCaller()
    {
        var elements = new List<BindingElement>();
        elements.Add(new BinaryMessageEncodingBindingElement());
        elements.Add(new HttpTransportBindingElement());
        var binding = new CustomBinding(elements);
        var endpointAddress = new EndpointAddress(App.GetRootUrl() + "Feed.svc");
        _channel = new ChannelFactory<IFeedService>(binding, endpointAddress).CreateChannel();
    }

    public void MakeCall(DateTime lastTime, Dictionary<string, string> context)
    {
        AsyncCallback asyncCallBack = delegate(IAsyncResult result)
        {
            var items = ((IFeedService)result.AsyncState).EndGet(result);
            if (ItemsRetrieved != null)
                ItemsRetrieved(this, new ServiceCallerEventArgs(items));
        };
        _channel.BeginGet(lastTime, context, asyncCallBack, _channel);
    }

    public event ItemsRetrievedEventHandler ItemsRetrieved;
    public event ServiceCallErrorHandler ServiceCallError;

    public delegate void ItemsRetrievedEventHandler(object sender, ServiceCallerEventArgs e);

    public delegate void ServiceCallErrorHandler(object sender, ServiceCallErrorEventArgs e);

    public void Dispose()
    {
        _channel.Close();
        _channel.Dispose();
    }
}

إليك تتبع المكدس لأولئك الذين لديهم فضول:

 An AsyncCallback threw an exception.
at System.ServiceModel.AsyncResult.Complete(Boolean completedSynchronously)
   at System.ServiceModel.AsyncResult.Complete(Boolean completedSynchronously, Exception exception)
   at System.ServiceModel.Channels.HttpChannelFactory.HttpRequestChannel.HttpChannelAsyncRequest.OnGetResponse(IAsyncResult result)
   at System.Net.Browser.BrowserHttpWebRequest.<>c__DisplayClassd.<InvokeGetResponseCallback>b__b(Object state2)
   at System.Threading._ThreadPoolWaitCallback.WaitCallback_Context(Object state)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading._ThreadPoolWaitCallback.PerformWaitCallbackInternal(_ThreadPoolWaitCallback tpWaitCallBack)
   at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback(Object state)

ولتحقيق ذلك، أقوم بتشغيل التطبيق في المتصفح، ثم أقوم بإيقاف عملية خادم الويب من Visual Studio.في بيئة الاختبار، أحصل على نفس الشيء عن طريق قطع اتصال الشبكة لنظام العميل.

إليك ToString () الاستثناء الكامل:

System.Exception: An AsyncCallback threw an exception. ---> System.Exception: An AsyncCallback threw an exception. ---> System.ServiceModel.CommunicationException: The remote server returned an error: NotFound. ---> System.Net.WebException: The remote server returned an error: NotFound. ---> System.Net.WebException: The remote server returned an error: NotFound.
   at System.Net.Browser.BrowserHttpWebRequest.InternalEndGetResponse(IAsyncResult asyncResult)
   at System.Net.Browser.BrowserHttpWebRequest.<>c__DisplayClass5.<EndGetResponse>b__4(Object sendState)
   at System.Net.Browser.AsyncHelper.<>c__DisplayClass2.<BeginOnUI>b__0(Object sendState)
   --- End of inner exception stack trace ---
   at System.Net.Browser.AsyncHelper.BeginOnUI(SendOrPostCallback beginMethod, Object state)
   at System.Net.Browser.BrowserHttpWebRequest.EndGetResponse(IAsyncResult asyncResult)
   at System.ServiceModel.Channels.HttpChannelFactory.HttpRequestChannel.HttpChannelAsyncRequest.CompleteGetResponse(IAsyncResult result)
   --- End of inner exception stack trace ---
   at System.ServiceModel.Channels.Remoting.RealProxy.Invoke(Object[] args)
   at proxy_2.EndGet(IAsyncResult )
   at CoasterBuzz.Feed.Client.ServiceCaller.<MakeCall>b__0(IAsyncResult result)
   at System.ServiceModel.AsyncResult.Complete(Boolean completedSynchronously)
   --- End of inner exception stack trace ---
   at System.ServiceModel.AsyncResult.Complete(Boolean completedSynchronously)
   at System.ServiceModel.AsyncResult.Complete(Boolean completedSynchronously, Exception exception)
   at System.ServiceModel.Channels.ServiceChannel.SendAsyncResult.CallComplete(Boolean completedSynchronously, Exception exception)
   at System.ServiceModel.Channels.ServiceChannel.SendAsyncResult.FinishSend(IAsyncResult result, Boolean completedSynchronously)
   at System.ServiceModel.Channels.ServiceChannel.SendAsyncResult.SendCallback(IAsyncResult result)
   at System.ServiceModel.AsyncResult.Complete(Boolean completedSynchronously)
   --- End of inner exception stack trace ---
   at System.ServiceModel.AsyncResult.Complete(Boolean completedSynchronously)
   at System.ServiceModel.AsyncResult.Complete(Boolean completedSynchronously, Exception exception)
   at System.ServiceModel.Channels.HttpChannelFactory.HttpRequestChannel.HttpChannelAsyncRequest.OnGetResponse(IAsyncResult result)
   at System.Net.Browser.BrowserHttpWebRequest.<>c__DisplayClassd.<InvokeGetResponseCallback>b__b(Object state2)
   at System.Threading._ThreadPoolWaitCallback.WaitCallback_Context(Object state)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading._ThreadPoolWaitCallback.PerformWaitCallbackInternal(_ThreadPoolWaitCallback tpWaitCallBack)
   at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback(Object state)

بالمناسبة، الطريقة الوحيدة التي يمكنني من خلالها اكتشاف ذلك على الإطلاق هي استخدام حدث UnhandledException على مستوى التطبيق في عميل SL.

هل كانت مفيدة؟

المحلول

وجيف، وأنا لست على يقين تماما من سؤالك كيف كنت محاكاة فشل الاتصال. أنا على افتراض أن "وكأن سيقوم الملقم لا يستجيب" يعني كنت تبحث عن نوع من الخطأ المهلة. ربما كنت محاكاة الخادم غير المراعية عن طريق إجبار بطء استجابة من خدمة الدعوة باستخدام Thread.Sleep () على جانب الملقم. حق؟ قريبة بما فيه الكفاية؟

وكان مشروع مماثل، لذلك أنا جربت هذا الخروج، وتمكنت من فخ مهلة والاستثناءات الأخرى بنجاح. أعتقد ربما أنك لم تكن رؤية هذه الاستثناءات بسبب المخصصة ملزمة زيارتها مهلة افتراضية طويلة جدا والتي قد لا يكون انتظرت فترة طويلة بما فيه الكفاية.

لشرح، ويتم التحكم مهلة WCF بواسطة تكوين ملزمة. النظر في التعليمات البرمجية الخاصة بك، كنت تقوم بإنشاء المخصصة الخاصة بك ملزمة مثل هذا:

var elements = new List<BindingElement>();
elements.Add(new BinaryMessageEncodingBindingElement());
elements.Add(new HttpTransportBindingElement());
var binding = new CustomBinding(elements);

إذا قمت بتعيين نقطة توقف هناك، وتفقد العرف الملزم الذي قمت بإنشائه، سوف ترى أن لديها دقيقة واحدة <لأ href = "http://msdn.microsoft.com/en-us/library/system.servicemodel. channels.binding.sendtimeout.aspx "يختلط =" نوفولو noreferrer "> SendTimeout افتراضيا. وأظن أنك إما لا تنتظر طويلا بما فيه الكفاية أم لا تحديد مهلة محاكاة الخدمة الخاص بك لفترة طويلة بما فيه الكفاية، لذلك لم تكن قادرة على التقاط استثناء المهلة.

وفيما يلي بعض التغييرات الرمز إلى التظاهر. أولا، لتعيين مهلة على الربط:

var binding = new CustomBinding(elements);
//Set the timeout to something reasonable for your service
//This will fail very quickly
binding.SendTimeout = TimeSpan.FromSeconds(1);

وأخيرا، للقبض على الاستثناءات ورفع أحداث صحيحة، يمكنك تحديث مندوب AsyncCallback على النحو التالي. تم طرح الاستثناء عندما يتم استدعاء EndGet ().

AsyncCallback asyncCallBack = delegate(IAsyncResult result)
{
    IFeedService service = ((IFeedService)result.AsyncState);
    try
    {
        var items = service.EndGet(result);
        ItemsRetrieved(this, EventArgs.Empty);
    }
    catch (System.TimeoutException ex)
    {
        //Handles timeout
        ServiceCallError(this, EventArgs.Empty);
    }
    catch (System.ServiceModel.CommunicationException ex)
    {
        //Handles a number of failures:
        //  Lack of cross-domain policy on target site
        //  Exception thrown by a service
        ServiceCallError(this, EventArgs.Empty);
    }
    catch (System.Exception ex)
    {
        //Handles any other errors here
        ServiceCallError(this, EventArgs.Empty);
    }
};

وبقدر مراجعة قانون العام، وأود أن ننصح تجنب الصعب الترميز التكوين ملزم الخاص بك. يمكنك بدلا من ذلك يعلن تكوين نقطة النهاية (بما في ذلك مهلة معقولة) في ملف ServiceReferences.ClientConfig والجديدة تصل مصنع قناتك مع اسم نقطة النهاية مثل هذا:

new ChannelFactory<IFeedService>("feedServiceEndpoint");

وهذا ينبغي أن تجعل التطبيق أكثر للصيانة مع مرور الوقت.

وآمل أن يساعد هذا.

وجيري

تحديث:

وجيف،

يرجى إلقاء نظرة على هذا الرمز والتعليقات ضمن لمزيد من التوضيح ما يحدث هنا. طريقة MakeCall () هو خلق وظيفة (مندوب) التي يتم تمريرها إلى الأسلوب BeginGet (). يتم تنفيذ هذه المهمة في وقت لاحق عندما تستجيب الخدمة.

يرجى إجراء التغييرات المقترحة وتعيين نقاط في خطوط بدءا "AsyncCallback asyncCallback" و "العناصر فار". سترى يمر أولا من خلال المصحح مجرد يعلن رمز (وظيفة / مندوب) لتنفيذ عندما تستجيب الخدمة ومسار الثاني هو المعالجة الفعلية من هذا الرد، بدءا من الداخل من إعلان المفوض. وهذا يعني أن محاولة الخارجي / الصيد لا تكون في نطاق عند معالجة استجابة خدمة الدعوة.

public void MakeCall(DateTime lastTime, Dictionary<string, string> context)
{
    try
    {
        AsyncCallback asyncCallBack = delegate(IAsyncResult result)
        {
            try
            {
                var items = ((IFeedService)result.AsyncState).EndGet(result);
                if (ItemsRetrieved != null)
                    ItemsRetrieved(this, new ServiceCallerEventArgs(items));
            }
            catch (Exception ex)
            { 
                //This will catch errors from the service call
            }
        };
        _channel.BeginGet(lastTime, context, asyncCallBack, _channel);
    }
    catch(Exception ex)
    {
        //This will not catch an error coming back from the service.  It will 
        //catch only errors in the act of calling the service asynchronously.

        //The communication with the service and response is handled on a different
        //thread, so this try/catch will be out of scope when that executes.

        //So, at this point in the execution, you have declared a delegate 
        //(actually an anonymous delegate the compiler will turn into a hidden class) 
        //which describes some code that will be executed when the service responds.

        //You can see this in action by setting a breakpoint just inside the delegate
        //at the line starting with "var items".  It will not be hit until the service
        // responds (or doesn't respond in a timeout situation).
    }
}

وجيري

نصائح أخرى

ولقد واجهت مشكلة مماثلة من قبل. والمشكلة الرئيسية هي مع الطريقة التي يتم بها تنفيذ IDisposable على الحالات الخاصة بك وكيل / قناة. ويرد الطريق لقد حلها في التعليمات البرمجية أدناه، حيث IDirector هو عقد خدمة بلدي:

public class ProxyWrapper : IDisposable
{
    private IDirector proxy;
    private ChannelFactory<IDirector> factory;
    int callCount = 0;

    public ProxyWrapper()
    {
        factory = new ChannelFactory<IDirector>();

        proxy = factory.CreateChannel();
    }

    public IDirector Proxy
    {
        get
        {
            if (callCount > 0)
                throw new InvalidOperationException("The proxy can only be accessed once for every ProxyWrapper instance. You must create a new ProxyWrapper instance for each service request.");
            // only allow the proxy/channel to be used for one call.

            callCount++;
            return proxy;
        }
    }

    public void Dispose()
    {
        IClientChannel channel = (IClientChannel)proxy;

        try
        {
            if (channel.State != CommunicationState.Faulted)
            {
                channel.Close();
            }
            else
            {
                channel.Abort();
            }
        }
        catch (CommunicationException)
        {
            channel.Abort();
        }
        catch (TimeoutException)
        {
            channel.Abort();
        }
        catch (Exception)
        {
            channel.Abort();
            throw;
        }
        finally
        {
            channel = null;
            proxy = null;
        }
    }
}

وطريقة الاستخدام الطبقة أعلاه هو كما يلي:

    public static void Login(string userName, string password)
    {
        using (ProxyWrapper wrapper = new ProxyWrapper())
        {
            currentSession = wrapper.Proxy.Login(userName, password);
        }
    }

ولأن الطبقة ProxyWrapper تنفذ IDisposable، إذا أردنا استخدام مثيل من فئة ProxyWrapper داخل كتلة using، ويضمن طريقة Dispose() ليتم استدعاؤها، حتى لو تم طرح استثناء. فإن التعليمات البرمجية في أسلوب Dispose() التعامل مع كل الحالات وحالات الوكيل / القناة. يمكنك بعد ذلك إضافة معالجة الأخطاء / تسجيل / الحدث كود مندوب الخاص في هذا الأسلوب.

وقراءة بلوق الدخول التالية لمزيد من المعلومات، وللحصول على نسخة أكثر عمومية من التعليمات البرمجية أعلاه: <لأ href = "http://bloggingabout.net/blogs/erwyn/archive/2006/12/09/WCF- خدمة وكيل-Helper.aspx "يختلط =" نوفولو noreferrer "> http://bloggingabout.net/blogs/erwyn/archive/2006/12/09/WCF-Service-Proxy-Helper.aspx

لالتقاط الأخطاء الاتصالات مع الخادم، وأنا أقترح وضع الدعوة إلى .CreateChannel() في محاولة ... الصيد والتعامل مع أشياء مثل CommunicationException وTimeoutExceptions هناك.

وأيضا، nuggest واحدة من النصائح العامة: خلق ChannelFactory هو تماما عملية مكلفة، لذلك قد ترغب في القيام بذلك بشكل منفصل وتخزين تلك إشارة إلى كائن ChannelFactory مكان

إذا كنت بحاجة إلى إنشاء وربما إعادة إنشاء قناة من ChannelFactory، يمكنك بعد ذلك استخدام هذا المخزن / المثال مؤقتا من ChannelFactory مرارا وتكرارا دون الحاجة إلى إنشاء ذلك في كل وقت.

ولكن بخلاف ذلك، وأود أن تعتقد أنك بخير تماما هنا!

ومارك

سيكون مثيل ChannelFactory الخاص بك في حالة "الإجهاض" عند حدوث هذه المهلة.ستجد أنه يتم طرح الاستثناء ليس عند حدوث الاستثناء في المكالمة، ولكن عند استدعاء _channel.Dispose().

ستحتاج إلى الحماية من هذا الاستثناء بطريقة ما.إن أبسط طريقة هي إجراء محاولة/التقاط حول محتويات طريقة التخلص الخاصة بك، ولكن سيكون من الأفضل إذا قمت بالتحقق من حالة مثيل _channel قبل محاولة الإغلاق/التخلص.

ولهذا السبب لا تزال تحصل على الاستثناء - ولا يتم التعامل معه هنا.

تحديث:

استنادًا إلى تتبع المكدس الخاص بك، يبدو أن إطار العمل يحاول تنفيذ مفوض رد الاتصال الخاص بك ويحصل على استثناء هناك.لقد أجبت في مكان آخر بأن محاولة/التقاط محتويات مندوبك لا يحل المشكلة...ماذا عن الاستثناءات الداخلية؟ما هو الإخراج الكامل لـ TheException.ToString ()؟

لقد قمت بالتجول عبر .NET Framework ويُسمح بالفعل بالسفر إلى مجموعة مؤشرات الترابط (على الأقل في مسار المكدس الموجود هنا) مما قد يتسبب في حدوث استثناء غير معالج وموت مؤشر الترابط الخاص بك.إذا أصبح الأسوأ أسوأ، يمكنك دائمًا تولي عملية الخيط...قم بتدوير مؤشر الترابط الخاص بك واستدعاء الخدمة بشكل متزامن، بدلاً من استخدام نمط المزامنة المدمج (ما لم يكن هذا نوع اتصال WCF مزدوج، وهو ما لا أعتقد أنه كذلك).

ما زلت أعتقد أن معلومات الاستثناء الكاملة (إذا كان هناك أي شيء آخر) قد تكون مفيدة.

وجيف، يرجى تبين لنا <م> الخاص بك الرمز عند محاولة وضع كتلة حاول / catch حول الدعوة EndGet. من الصعب أن نصدق أن لا يصاب استثناء، ورؤيتها سيساعدني الاعتقاد.

وأيضا كجزء من هذه التجربة، والتخلص من معالج الحدث UnhandledException. أعتقد أنك اصطياد هذا الاستثناء بمجرد BrowserHttpRequest يدرك هناك 404. أعتقد أنه من دون هذا الحدث UnhandledException، وحاول / catch حول EndGet سوف تصفية الاستثناء، إن وجدت. أعتقد أنك التقاط حالة وسطية من عملية معالجة الاستثناء.

وأود أيضا أن أراك وضع System.Debugger.Break أو شيء من هذا على الفور بعد المكالمة EndGet.

هل أنا على حق في التفكير التي قمنا بتصميم الطبقة ServiceCaller بحيث يمكنك تسميتها في كتلة using؟

وإذا كانت الإجابة بنعم، ثم هذه هي المشكلة. وقد تم تصميم الطبقات قناة WCF في مثل هذه الطريقة لتجعل من الصعب للغاية لكتابة رمز التخلص العام تماما أن يلبي كل الشروط الخطأ المحتملة. لو كانت بسيطة، ثم الطبقات القناة نفسها أن تكون آمنة للتخلص وأنك لن تحتاج إلى فئة المجمع.

والطريقة الآمنة الوحيدة لقد وجدت حتى الآن هي لتغليف لا منطق التصرف، ولكن تسلسل دعوة بأكمله، على النحو التالي:

public static void Invoke<TContract>(ChannelFactory<TContract> factory, Action<TContract> action) where TContract : class {

    var proxy = (IClientChannel) factory.CreateChannel();
    bool success = false;
    try {
        action((TContract) proxy);
        proxy.Close();
        success = true;
    } finally {
        if(!success) {
            proxy.Abort();
        }
    }
}

وأنا أعلم أن هذا مكالمة متزامن، ولكن المبدأ هو نفسه. بدلا من أنت تحاول أن نستنتج ما إذا كان يجب استدعاء Close أو Abort، يمكنك تحديد ذلك مقدما على أساس مدى من خلال المكالمة التي تمكن من الحصول قبل ان تسقط من جديد.

ملحوظة - فقط قناة الفعلية فإن هذه المعاملة الخاصة. يمكنك التخلص من المصنع بالطريقة التي تريدها AFAIK.

وأعتقد أن المشكلة هي في الطريقة التي قمنا بتصميم المتصل الخدمة.

ويتم فتح قناة عند إنشاء المتصل الخدمة. وهذا يعني أن القناة يمكن مهلة وليس هناك شيء في التعليمات البرمجية التي يمكن استرداد من timeount.

وأود الانتقال خلق وإغلاق القناة لطريقة جعل المكالمة.

ويمكنك القيام صيد المحاولة حول بدء الحصول على وحول محتويات الاستدعاء.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top