在调用GetResponseAsync()期间获取InvalidOperationException?

时间:2013-05-21 08:16:02

标签: c# windows-phone-7 async-await nullreferenceexception invalidoperationexception

前言:我知道代码摘录很冗长,但我不想遗漏其他人可能会发现问题的原因。代码有点冗长的原因和许多异常陷阱的原因是由于我在下面描述的 NullReferenceException 的搜索。通过跳转到相互调用的 async 方法中的等待关键字,您可以快速浏览代码到显着部分。

UPDATE:发生InvalidOperationException,因为我正在改变某些按钮的IsEnabled状态。我在主线程上,所以我不确定为什么会发生这种情况。有谁知道为什么?

我有一个编写C#的Windows Phone 7应用程序,当在特定代码上下文中调用 GetResponseAsync()时,它会获得 System.InvalidOperationException 。该应用程序使用PetFinder API创建一个Cat Breed Guessing游戏,旨在帮助动物收容所中的猫被采用。以下是完整的Exception消息:

Message: An unhandled exception of type 'System.InvalidOperationException' occurred in System.Windows.ni.dll

在异常发生之前,有几次成功调用 GetResponseAsync()。我按照下面调用的顺序包含了Exception中涉及的方法的代码。有人能告诉我为什么我得到这个例外以及如何解决它?

Exception完全是在调用它的当前代码上下文中发生的,因此下面的代码与包含 GetResponseAsync()的库之间的某些交互正在创建问题的条件。在调用 GetResponseAsync()之前的线程代码上下文是主线程

背景说明:

这一切都是在我追逐在 doLoadRandomPet()中调用 getRandomPetExt()期间发生的NullReferenceException时开始的。根据我在SO上的阅读,我的猜测是从 getRandomPetExt()返回 NULL任务。但是如果你看一下这段代码,你会发现我正在尽我所能来捕获一个虚假的异常并避免返回一个NULL任务。我目前的信念仍然是NULL任务正在发生,因为其他一些代码在我的代码之外产生了一个虚假的异常。也许是 Microsoft.Bcl.Async 中的某些内容?这是一些奇怪的同步上下文问题还是隐藏的跨线程访问问题?

奇怪的是,在我做出特定更改之前,我根本没有得到 InvalidOperationException ,只有间歇性的 NullReferenceException 每20到30次调用一次方法链如下所示。另一方面 InvalidOperationException 每次都会发生新代码结构。我做的改变对我来说是一个小问题,旨在帮助我的调试工作。我唯一做的就是创建一个方法包装器,将 loadRandomPet()的内容移动到 doLoadRandomPet()中。我做了这个,所以我可以禁用一些触发方法调用的按钮,这些按钮可能会干扰操作以获得随机宠物。我在try / finally块中包含对 doLoadRandomPet()的调用,以确保在操作退出时重新启用按钮。为什么这会导致代码执行发生如此重大的变化?

    async private void loadRandomPet(int maxRetries = 3)
    {
        // Do not allow the Guess My Breed or Adopt Me buttons to be 
        //  clicked while we are getting the next pet.
        btnAdoptMe.IsEnabled = false;
        btnGuessMyBreed.IsEnabled = false;

        try
        {
            await doLoadRandomPet(maxRetries);
        }
        finally
        {
    // >>>>> THIS CODE IS NEVER REACHED.

            // Make sure the buttons are re-enabled.
            btnAdoptMe.IsEnabled = true;
            btnGuessMyBreed.IsEnabled = true;
        }
    }

    // -------------------- CALLED NEXT

    /// <summary>
    /// (awaitable) Loads a random pet with a limit on the number of retries in case of failure.
    /// </summary>
    /// <param name="maxRetries">The number of retries permitted.</param>
    async private Task doLoadRandomPet(int maxRetries = 3)
    {
        // Show the busy indicator.
        radbusyMain.Visibility = Visibility.Visible;

        try
        {

            // Get a random pet.
            List<KeyValuePair<string, string>> listUrlArgs = new List<KeyValuePair<string, string>>();

            // Only cats.
            listUrlArgs.addKVP("animal", PetFinderUtils.EnumAnimalType.cat.ToString());

            if (!String.IsNullOrWhiteSpace(MainMenu.ZipCode))
            {
                listUrlArgs.addKVP(PetFinderUtils.URL_FIELD_LOCATION, MainMenu.ZipCode);
            }

            if (maxRetries < 0)
                throw new ArgumentOutOfRangeException("The maximum retries value is negative.");

            Debug.WriteLine("------------------ START: LOADING Random Pet ----------------");

            // Loop until a random pet is found.
            int numRetries = 0;

            // Select the breed, otherwise we will get a ton of "Domestic Short Hair" responses,
            //  which are not good for the game.  Breeds that are returning empty search
            //  results this session are filtered too.
            string strBreedName = MainMenu.GetRandomBreedName();

            listUrlArgs.addKVP("breed", strBreedName);

            while (numRetries <= maxRetries)
            {
                try
                {
                    // Save the last successful retrieval.
                    if (this._pbi != null)
                        _pbiLast = this._pbi;

                    this._pbi = await getRandomPetExt(listUrlArgs);
                }
                catch (EmptySearchResultsException esr)
                {
                    // getRandomPetExt() could not find a suitable cat given the current parameters.
                    //  Swallow the Exception without notifying the user.  Just allow the code
                    //  further down to re-use the last cat retrieved in the hopes the next
                    //  quiz won't have the problem.  
                    Debug.WriteLine(">>>>>>>>>> doLoadRandomPet() - getRandomPet() failed to find a cat.");
                }
                catch (PetFinderApiException pfExc)
                {
                    if (pfExc.ResponseCode == PetFinderUtils.EnumResponseCodes.PFAPI_ERR_LIMIT)
                        // Swallow the Exception, but let the user know to stop playing for the awhile
                        //  since we have exceeded our rate limit.
                        CatQuizAux.EasyToast("The PetFinder server is busy.\nPlease try playing the game\nlater.");
                    else
                        // Swallow the Exception, but let the user know to stop playing for the awhile
                        //  since we have exceeded our rate limit.
                        CatQuizAux.EasyToast("The PetFinder may be down.\nPlease try playing the game\nlater.");

                    // Just exit.
                    return;
                } // try
                catch (Exception exc)
                {
                    // This is really bad practice but we're out of time.   Just swallow the Exception
                    //  to avoid crashing the program.
                    Debug.WriteLine(">>>>>>>>>> doLoadRandomPet() - getRandomPet() Other Exception occurrred.  Exception Message: " + exc.Message);
                }


                // If the returned pet is NULL then no pets using the current search criteria
                //  could be found.
                if (this._pbi != null)
                {
                    // Got a random pet, stop looping.  Save it to the backup cat field too.
                    break;
                }
                else
                {
                    // Are we using a location?
                    if (listUrlArgs.hasKey(PetFinderUtils.URL_FIELD_LOCATION))
                        // Retry without the location to open the search to the entire PetFinder API
                        //  inventory.
                        listUrlArgs.deleteKVP(PetFinderUtils.URL_FIELD_LOCATION);
                    else
                    {
                        // Use a differet breed.  Add the current breed to the list of breeds returning
                        //  empty search results so we don't bother with that breed again this session.
                        MainMenu.ListEmptyBreeds.Add(strBreedName);

                        // Remove the current breed.
                        listUrlArgs.deleteKVP("breed");

                        // Choose a new breed.
                        strBreedName = MainMenu.GetRandomBreedName();
                        listUrlArgs.addKVP("breed", strBreedName);
                    } // else - if (listUrlArgs.hasKey(PetFinderUtils.URL_FIELD_LOCATION))
                } // if (this._pbi == null)

                // Sleep a bit.
                await TaskEx.Delay(1000);

                numRetries++;
            } // while (numRetries <= maxRetries)

            // If we still have a null _pbi reference, use the back-up one.
            if (this._pbi == null)
                this._pbi = this._pbiLast;

            if (this._pbi == null)
                throw new ArgumentNullException("(ViewPetRecord::doLoadRandomPet) Failed completely to find a new cat for the quiz.  Please try again later.");

            // Add the pet to the already quizzed list.
            MainMenu.AddCatQuizzed(this._pbi.Id.T.ToString());

            // Show the cat's details.
            lblPetName.Text = this._pbi.Name.T;
            imgPet.Source = new BitmapImage(new Uri(this._pbi.Media.Photos.Photo[0].T, UriKind.Absolute));

            // Dump the cat's breed list to the Debug window for inspection.
            dumpBreedsForPet(this._pbi);
        }
        finally
        {
            // Make sure the busy indicator is hidden.
            radbusyMain.Visibility = Visibility.Collapsed;
        }
    } // async private void doLoadRandomPet(int maxRetries = 3)

    // -------------------- CALLED NEXT

    /// <summary>
    /// Gets a Random Pet.  Retries up to maxRetries times to find a pet not in the already <br />
    ///  quizzed list before giving up and returning the last one found.  Also skips pets without <br />
    ///  photos.
    /// </summary>
    /// <param name="listUrlArgs">A list of URL arguments to pass add to the API call.</param>
    /// <param name="maxRetries">The number of retries to make.</param>
    /// <returns>The basic info for the retrieved pet or NULL if a pet could not be found <br />
    ///  using the current URL arguments (search criteria).</returns>
    async private Task<PetBasicInfo> getRandomPetExt(List<KeyValuePair<string, string>> listUrlArgs, int maxRetries = 3)
    {
        PetBasicInfo newPbi = null;

        try
        {
            newPbi = await doGetRandomPetExt(listUrlArgs, maxRetries);
        }
        catch (Exception exc)
        {
            Debug.WriteLine(">>>>>> (ViewPetRecord::getRandomPetExt) EXCEPTION: " + exc.Message);
            throw;
        } // try/catch

        return newPbi;
    } // async private void getRandomPetExt()

    // -------------------- CALLED NEXT

    // This was done just to help debug the NullReferenceException error we are currently fighting.
    //  see getRandomPetExt() below.
    async private Task<PetBasicInfo> doGetRandomPetExt(List<KeyValuePair<string, string>> listUrlArgs, int maxRetries = 3)
    {
        if (maxRetries < 0)
            throw new ArgumentOutOfRangeException("The maximum retries value is negative.");

        Debug.WriteLine("------------------ START: Getting Random Pet ----------------");

        // Loop until a random pet is found that has not already been used in the quiz or until
        //  we hit the maxRetries limit.
        int numRetries = 0;

        PetBasicInfo pbi = null;

        while (numRetries <= maxRetries)
        {
            try
            {
                pbi = await MainMenu.PetFinderAPI.GetRandomPet_basic(listUrlArgs);
            }
            catch (PetFinderApiException pfExcept)
            {
                // pfExcept.ResponseCode = PetFinderUtils.EnumResponseCodes.PFAPI_ERR_LIMIT;

                switch (pfExcept.ResponseCode)
                {
                    case PetFinderUtils.EnumResponseCodes.PFAPI_ERR_NOENT:
                        Debug.WriteLine("The PetFinder API returned an empty result set with the current URL arguments.");
                        // No results found.  Swallow the Exception and return
                        //  NULL to let the caller know this.
                        return null;
                    case PetFinderUtils.EnumResponseCodes.PFAPI_ERR_LIMIT:
                        Debug.WriteLine("The PetFinder API returned a rate limit error.");
                        // Throw the Exception.  Let the caller handler it.
                        throw;
                    default:
                        // Rethrow the Exception so we know about it from the crash reports.
                        // Other Exception.  Stop retrying and show the user the error message.
                        Debug.WriteLine("Exception during getRandomPetExt()\n" + pfExcept.ErrorMessage);
                        throw;
                } // switch()
            }

            // Does the pet have a photo?
            if (pbi.Media.Photos.Photo.Length > 0)
            {
                // Yes. Has the pet already been used in a quiz?
                if (!MainMenu.IsCatQuizzed(pbi.Id.T.ToString()))
                    // No. Success.
                    return pbi;
            } // if (pbi.Media.Photos.Photo.Length > 0)

            // Retry required.
            Debug.WriteLine(String.Format("Retrying, retry count: {0}", numRetries));

            // No photo or already used in a quiz.  Wait a little before retrying.
            await TaskEx.Delay(1000);

            // Count retires.
            numRetries++;
        } // while (numRetries <= maxRetries)

        // Unable to find a cat not already quizzed.  Just return the last retrieved.
        Debug.WriteLine("Retry count exceeded.  Returning last retreived pet.");

        // Returning NULL results in a await throwing a non-specific NullReferenceException.  
        //  Better to throw our own Exception.
        throw new EmptySearchResultsException("(ViewPetRecord::getRandomPetExt) Unable to retrieve a new random cat from the PetFinder API server.");
        // return pbi;
    } // async private PetBasicInfo doGetRandomPetExt()

    // ------------------ CALLED NEXT

    /// <summary>
    /// Returns the basic information for a randomly chosen pet of the given animal type.
    /// </summary>
    /// <param name="enAnimalType">The desired animal type to restrict the search to.</param>
    /// <returns></returns>
    async public Task<JSON.JsonPetRecordTypes.PetBasicInfo> GetRandomPet_basic(List<KeyValuePair<string, string>> urlArgumentPairs = null)
    {
        Debug.WriteLine("(GetRandomPet_basic) Top of call.");

        // If the URL Argument Pairs parameter is null, then create one.
        if (urlArgumentPairs == null)
            urlArgumentPairs = new List<KeyValuePair<string, string>>();

        // Add the "output" parameter that tells PetFinder we want the Basic information for the pet,
        //  not the ID or full record.
        urlArgumentPairs.addKVP("output", "basic");

        // Add a unique URL argument to defeat URL caching that may be taking
        //  place in the Windows Phone library or at the PetFinder API server.
        //  This defeats the problem so that a new random pet is returned
        //  each call, instead of the same one.
        long n = DateTime.Now.Ticks;

        urlArgumentPairs.addKVP("nocache", n.ToString());

        // Build the API call.
        string strApiCall =
                buildPetFinderApiUrl(METHOD_RANDOM_PET,
                urlArgumentPairs);

        Debug.WriteLine("(GetRandomPet_basic) URL for call: \n" + strApiCall);

        // Make the call.
        string strJsonReturn = await Misc.URLToStringAsyncEZ(strApiCall);

        bool bIsJsonReturnValid = false;

        try
        {
            JSON.JsonPetRecordTypes.PetRecordBasicInfo jsonPetBasic = JsonConvert.DeserializeObject<JSON.JsonPetRecordTypes.PetRecordBasicInfo>(strJsonReturn);

            // Deserialization succeeded.
            bIsJsonReturnValid = true;

            // Success code?
            // For some strange reason T is cast to an "int" here where in GetBreedList it's equivalent is cast to a string.
            int iResponseCode = jsonPetBasic.Petfinder.Header.Status.Code.T;

            if (iResponseCode != 100)
                throw new PetFinderApiException("PetFinder::GetRandomPet_basic", iResponseCode);
                // throw new Exception("(PetFinder::GetRandomPet_basic) The response document contains a failure response code: " + iResponseCode.ToString() + ":" + jsonPetBasic.Petfinder.Header.Status.Message);

            // Return the pet record basic info.
            return jsonPetBasic.Petfinder.Pet;
        }
        finally
        {
            if (!bIsJsonReturnValid)
                // Setting debug trap to inspect JSON return.
                Debug.WriteLine("JSON Deserialization failure.");

            Debug.WriteLine("(GetRandomPet_basic) BOTTOM of call.");
        } // try/finally
    }

    // -------------------- CALLED NEXT, never returns

    /// <summary>
    /// (awaitable) Simpler version of above call.  Same warnings about getting byte stream <br />
    ///  objects apply here as they do to URLtoStringAsync()
    /// </summary>
    /// <param name="stUrl"></param>
    /// <param name="reqMethod"></param>
    /// <returns></returns>
    async public static Task<string> URLToStringAsyncEZ(string strUrl, HttpRequestMethod reqMethod = HttpRequestMethod.HTTP_get)
    {
        strUrl = strUrl.Trim();

        if (String.IsNullOrWhiteSpace(strUrl))
            throw new ArgumentException("(Misc::URLToStringAsyncEZ) The URL is empty.");

        HttpWebRequest request = (HttpWebRequest)WebRequest.Create(strUrl);

        // Get the string value for the request method.
        request.Method = reqMethod.GetDescription();

    // >>>>> THIS CALL to GetResponseAsync() TRIGGERS THE EXCEPTION (see stack trace below)
        // Async wait for the respone.
        HttpWebResponse response = (HttpWebResponse)await request.GetResponseAsync();

        // Use a stream reader to return the string.
        using (var sr = new StreamReader(response.GetResponseStream()))
        {
            return sr.ReadToEnd();
        }
    }

// -------------------- STACK TRACE JUST BEFORE URLToStringAsync(), the call the triggers the exception.

>   Common_WP7.DLL!Common_WP7.Misc.URLToStringAsyncEZ(string strUrl, Common_WP7.Misc.HttpRequestMethod reqMethod) Line 1079 C#
CatQuiz.DLL!CatQuiz.PetFinderUtils.GetRandomPet_basic(System.Collections.Generic.List<System.Collections.Generic.KeyValuePair<string,string>> urlArgumentPairs) Line 441    C#
CatQuiz.DLL!CatQuiz.ViewPetRecord.doGetRandomPetExt(System.Collections.Generic.List<System.Collections.Generic.KeyValuePair<string,string>> listUrlArgs, int maxRetries) Line 55    C#
CatQuiz.DLL!CatQuiz.ViewPetRecord.getRandomPetExt(System.Collections.Generic.List<System.Collections.Generic.KeyValuePair<string,string>> listUrlArgs, int maxRetries) Line 123 C#
CatQuiz.DLL!CatQuiz.ViewPetRecord.doLoadRandomPet(int maxRetries) Line 243  C#
CatQuiz.DLL!CatQuiz.ViewPetRecord.loadRandomPet(int maxRetries) Line 343    C#
CatQuiz.DLL!CatQuiz.ViewPetRecord.PageViewPetRecord_Loaded(object sender, System.Windows.RoutedEventArgs e) Line 355    C#
System.Windows.ni.dll!MS.Internal.CoreInvokeHandler.InvokeEventHandler(int typeIndex, System.Delegate handlerDelegate, object sender, object args)  Unknown
System.Windows.ni.dll!MS.Internal.JoltHelper.FireEvent(System.IntPtr unmanagedObj, System.IntPtr unmanagedObjArgs, int argsTypeIndex, int actualArgsTypeIndex, string eventName)    Unknown

    ======================= EXCEPTION

// -------------------- STACK TRACE when EXCEPTION occurs
>   CatQuiz.DLL!CatQuiz.App.Application_UnhandledException(object sender, System.Windows.ApplicationUnhandledExceptionEventArgs e) Line 101 C#
System.Windows.ni.dll!MS.Internal.Error.CallApplicationUEHandler(System.Exception e)    Unknown
System.Windows.ni.dll!MS.Internal.Error.IsNonRecoverableUserException(System.Exception ex, out uint xresultValue)   Unknown
System.Windows.ni.dll!MS.Internal.JoltHelper.FireEvent(System.IntPtr unmanagedObj, System.IntPtr unmanagedObjArgs, int argsTypeIndex, int actualArgsTypeIndex, string eventName)    Unknown


    // -------------------- CODE CONTEXT when EXCEPTION occurs
    // Code to execute on Unhandled Exceptions
    private void Application_UnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e)
    {
        if (System.Diagnostics.Debugger.IsAttached)
        {
            // An unhandled exception has occurred; break into the debugger
            System.Diagnostics.Debugger.Break();
        }
    }

0 个答案:

没有答案