C# 在调用GetResponseAsync()期间获取InvalidOperationException?
前言:我知道代码摘录很长,但我不想遗漏一个其他人可能会认为是问题原因的细节。代码有点冗长,并且存在许多异常陷阱,这是因为我在下面描述了对NullReferenceException的搜索。通过跳转到在相互调用的async方法中找到的wait关键字,您可以快速浏览代码,找到重要部分 更新:InvalidOperationException正在发生,因为我正在更改某些按钮的IsEnabled状态。我处于主线上,所以我不确定为什么会发生这种情况。有人知道为什么吗 我有一个用C语言编写的Windows Phone 7应用程序,它得到一个系统。当在特定代码上下文中调用GetResponseAsync()时,InvalidOperationException。该应用程序使用PetFinder API创建一个猫品种猜测游戏,旨在帮助动物收容所的猫被收养。以下是完整的异常消息:C# 在调用GetResponseAsync()期间获取InvalidOperationException?,c#,windows-phone-7,async-await,nullreferenceexception,invalidoperationexception,C#,Windows Phone 7,Async Await,Nullreferenceexception,Invalidoperationexception,前言:我知道代码摘录很长,但我不想遗漏一个其他人可能会认为是问题原因的细节。代码有点冗长,并且存在许多异常陷阱,这是因为我在下面描述了对NullReferenceException的搜索。通过跳转到在相互调用的async方法中找到的wait关键字,您可以快速浏览代码,找到重要部分 更新:InvalidOperationException正在发生,因为我正在更改某些按钮的IsEnabled状态。我处于主线上,所以我不确定为什么会发生这种情况。有人知道为什么吗 我有一个用C语言编写的Windows
Message: An unhandled exception of type 'System.InvalidOperationException' occurred in System.Windows.ni.dll
在异常发生之前,对GetResponseAsync()有几个成功的调用。我已经按照下面调用的顺序包含了异常中涉及的方法的代码。有人能告诉我为什么会出现这个异常以及如何修复它吗
异常完全是在调用它的当前代码上下文之外发生的,因此下面的代码和包含GetResponseAsync()的库之间的一些交互为问题创造了条件。调用GetResponseAsync()之前的线程代码上下文是主线程
背景说明:
这一切都始于我在调用doLoadRandomPet()中的getRandomPetExt()时发现的NullReferenceException。根据我在上面所做的阅读,我猜测是从getRandomPetExt()返回了一个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();
}
}
async private void loadRandomPet(int-maxRetries=3)
{
//不要让“猜我的品种”或“领养我的纽扣”被忽略
//当我们得到下一只宠物时点击。
btnAdoptMe.IsEnabled=false;
btnguesmybreed.IsEnabled=false;
尝试
{
等待doLoadRandomPet(最大重试次数);
}
最后
{
//>>>>>>>永远不会到达此代码。
//确保按钮已重新启用。
btnAdoptMe.IsEnabled=true;
btnguesmybreed.IsEnabled=true;
}
}
//---------下一步调用
///
///(等待)加载随机宠物,并限制失败时的重试次数。
///
///允许的重试次数。
异步专用任务doLoadRandomPet(int maxRetries=3)
{
//显示忙指示灯。
radbusyMain.Visibility=可见性.Visibility;
尝试
{
//随机得到一只宠物。
List listUrlArgs=新列表();
//只有猫。
listUrlArgs.addKVP(“动物”,PetFinderUtils.enumanialtype.cat.ToString());
如果(!String.IsNullOrWhiteSpace(main menu.ZipCode))
{
listUrlArgs.addKVP(PetFinderUtils.URL\u FIELD\u位置,main menu.ZipCode);
}
如果(最大重试次数<0)
抛出新ArgumentOutOfRangeException(“最大重试次数值为负值”);
Debug.WriteLine(“--------------开始:加载随机Pet--------------------”;
//循环直到找到随机的宠物。
int numRetries=0;
//选择品种,否则我们会得到大量的“国产短发”回复,
//这对游戏不好。返回空搜索的品种
//此会话的结果也将被筛选。
字符串strBreedName=MainMenu.GetRandomBreedName();
listUrlArgs.addKVP(“品种”,strBreedName);
while(numRetries>>>>此对GetResponseAsync()的调用会触发异常(请参阅下面的堆栈跟踪)
//异步等待响应。
HttpWebResponse=(HttpWebResponse)等待请求。GetResponseAsync();
//使用流读取器返回字符串。
使用(var sr=newstreamreader(response.GetResponseStream())
{
返回sr.ReadToEnd();