PREFACE: I know the code excerpt is lengthy but I did't want to leave out a detail that someone else might spot as the cause of the issue. The reason for the somewhat verbose nature of the code and the many Exception traps is due to the hunt for the NullReferenceException that I describe below. You can wade through the code quickly to the salient parts by jumping to the await keywords found amongst the async methods that call each other.
UPDATE: The InvalidOperationException is occurring because I am altering the IsEnabled status of certain buttons. I am on the Main thread so I am not sure why this is happening. Does anyone know why?
I have a Windows Phone 7 application written C# that is getting a System.InvalidOperationException when GetResponseAsync() is called in a particular code context. The application uses the PetFinder API to create a Cat Breed Guessing game with the intention of helping cats in animal shelters get adopted. Here is the exact Exception message in its entirety:
Message: An unhandled exception of type 'System.InvalidOperationException' occurred in System.Windows.ni.dll
Before the Exception occurs, there are several successful calls to GetResponseAsync(). I have included the code for the methods involved in the Exception in the order that they are called below. Can someone tell me why I am getting this Exception and how to fix it?
The Exception is occurring completely out of the current code context that calls it, so it's some interaction between the code below and the library that contains GetResponseAsync() that is creating the conditions for the problem. The thread code context just before the call to GetResponseAsync() is the Main thread.
BACKGROUND NOTE:
This all began while I was chasing down a NullReferenceException that was occuring during the call to getRandomPetExt() within doLoadRandomPet(). From the reading I did on SO, my guess is that a NULL Task was being returned from getRandomPetExt(). But if you look at that code you'll see I'm doing everything in my power to trap a spurious Exception and to avoid returning a NULL Task. My current belief still at this time is that the NULL Task is occurring because some other code is generating a spurious Exception outside of my code. Perhaps something in Microsoft.Bcl.Async? Is this some strange synchronization context problem or hidden cross-thread access issue?
The strange part is that before I made a particular change I did not get the InvalidOperationException at all, only the intermittent NullReferenceException every 1 out of 20 to 30 calls to the method chain shown below. The InvalidOperationException on the other hand happens every time with the new code structure. The change I made was to me a minor one meant to help my debugging efforts. The only thing I did was create a method wrapper that moved the guts of loadRandomPet() into doLoadRandomPet(). I did this so I could disable some buttons that triggered method calls that might interfere with the operation to get a random pet. I wrapped the call to doLoadRandomPet() in a try/finally block, to make sure the buttons were re-enabled when the operation exited. Why would this cause such a major change in code execution?
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();
}
}
Related
Per my code below. I'm testing UI to ensure correct permissions for a application are applied to certain user accounts.
When I run the below, the test passes at "Assert.Pass(); //marks as a pass here ok".
Following this, code drops into the catch block.
The exception message is literally blank, the stack trace is:
at NUnit.Framework.Assert.Pass(String message, Object[] args) at
NUnit.Framework.Assert.Pass() at
SigangeCoreUiTests.Tests.PermissionTests.BslUserPermissionTest() in
C:...\PermissionTests.cs:line 90
The test then obviously fails because of the assert fail, however there is no reason for the exception to be raised and it already passed. The screen shot capture is not raising an issue, it seems to be the Assert.Pass.
Why might my code do this?
public void BslUserPermissionTest()
{
var _testUsername = _configuration.GetValue<string>("PermissionsTestUserList:BSLUser:Username");
var _testPassword = _configuration.GetValue<string>("PermissionsTestUserList:BSLUser:Password");
var wait = new WebDriverWait(_driver, new TimeSpan(0, 0, 60));
try
{
if (!LoginHandler(_testUsername, _testPassword))
{
Assert.Fail();
}
System.Threading.Thread.Sleep(2000);
var menuItem = wait.Until(ExpectedConditions.ElementIsVisible(By.LinkText("BSL Interpreter")));
menuItem.Click();
var result = wait.Until(ExpectedConditions.ElementIsVisible(By.TagName("app-bsl-interpreter")));
if (result.Text.Contains("Message for Interpretation"))
{
//test no other menu items are present - only expecting the one menu item
if (ValidateSingleMenuItemPresent())
{
new SupportClasses.ScreenShot().Take(_driver, false);
Assert.Fail();
}
new SupportClasses.ScreenShot().Take(_driver, true);
Assert.Pass(); //marks as a pass here ok
}
else
{
new SupportClasses.ScreenShot().Take(_driver, false);
Assert.Fail();
}
}
catch (Exception ex) //raises exception for unknown reason
{
new SupportClasses.ScreenShot().Take(_driver, false);
Assert.Fail();
}
finally
{
_driver = new SupportClasses.SsdAuthentiation(_driver).Logout();
}
}
Assert.Pass throws exception, you should use Assert to check values as per your test case.
See the below code from GitHub.
/// <summary>
/// Throws a <see cref="SuccessException"/> with the message and arguments
/// that are passed in. This allows a test to be cut short, with a result
/// of success returned to NUnit.
/// </summary>
[DoesNotReturn]
static public void Pass()
{
Assert.Pass(string.Empty, null);
}
Before I use Nito.MVVM, I used plain async/await and it was throwing me an aggregate exception and I could read into it and know what I have. But since Nito, my exceptions are ignored and the program jumps from async code block and continue executes. I know that it catch exceptions because when I put a breakpoint on catch(Exception ex) line it breaks here but with ex = null. I know that NotifyTask has properties to check if an exception was thrown but where I put it, it checks when Task is uncompleted, not when I need it.
View model:
public FileExplorerPageViewModel(INavigationService navigationService)
{
_navigationService = navigationService;
_manager = new FileExplorerManager();
Files = NotifyTask.Create(GetFilesAsync("UniorDev", "GitRemote/GitRemote"));
}
Private method:
private async Task<ObservableCollection<FileExplorerModel>> GetFilesAsync(string login, string reposName)
{
return new ObservableCollection<FileExplorerModel>(await _manager.GetFilesAsync(login, reposName));
}
Manager method(where exception throws):
public async Task<List<FileExplorerModel>> GetFilesAsync(string login, string reposName)
{
//try
//{
var gitHubFiles = await GetGitHubFilesAsync(login, reposName);
var gitRemoteFiles = new List<FileExplorerModel>();
foreach ( var file in gitHubFiles )
{
if ( file.Type == ContentType.Symlink || file.Type == ContentType.Submodule ) continue;
var model = new FileExplorerModel
{
Name = file.Name,
FileType = file.Type.ToString()
};
if ( model.IsFolder )
{
var nextFiles = await GetGitHubFilesAsync(login, reposName);
var count = nextFiles.Count;
}
model.FileSize = file.Size.ToString();
gitRemoteFiles.Add(model);
}
return gitRemoteFiles;
//}
//catch ( WebException ex )
//{
// throw new Exception("Something wrong with internet connection, try to On Internet " + ex.Message);
//}
//catch ( Exception ex )
//{
// throw new Exception("Getting ExplorerFiles from github failed! " + ex.Message);
//}
}
With try/catch or without it has the same effect. This behavior is anywhere where I have NotifyTask.
Update
There is no event, that fires when exception occurred, but there is Property Changed event, so I used it and added this code:
private void FilesOnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
{
throw new Exception("EXCEPTION");
bool failed;
if ( Files.IsFaulted )
failed = true;
}
And exception not fires.
I added throw exception in App class (main class) and it fired. And when I have exceptions that come from XAML, it also fires. So maybe it not fires when it comes from a view model, or something else. I have no idea. Will be very happy for some help with it.
Update
We deal with exception = null, but the question is still alive. What I wanna add, that I rarely this issue, when the app is starting to launch on the physic device. I read some info about it, and it doesn't seem to be related, but maybe it is:
I'm not entirely sure what your desired behavior is, but here's some information I hope you find useful.
NotifyTask is a data-bindable wrapper around Task. That's really all it does. So, if its Task faults with an exception, then it will update its own data-bindable properties regarding that exception. NotifyTask is intended for use when you want the UI to respond to a task completing, e.g., show a spinner while the task is in progress, an error message if the task faults, and data if the task completes successfully.
If you want your application to respond to the task faulting (with code, not just a UI update), then you should use try/catch like you have commented out in GetFilesAsync. NotifyTask doesn't change how those exceptions work; they should work just fine.
I know that it catch exceptions because when I put a breakpoint on catch(Exception ex) line it breaks here but with ex = null.
That's not possible. I suggest you try it again.
I know that NotifyTask has properties to check if an exception was thrown but where I put it, it checks when Task is uncompleted, not when I need it.
If you really want to (asynchronously) wait for the task to complete and then check for exceptions, then you can do so like this:
await Files.TaskCompleted;
var ex = Files.InnerException;
Or, if you just want to re-raise the exception:
await Files.Task;
Though I must say this usage is extremely unusual. The much more proper thing to do is to have a try/catch within your GetFilesAsync.
I am developing in C# for the Motorola device "MC67" and I have having issues with initialising the scanner.
The code I am using seems to be generic as I have found similar examples all over the Internet; for reference here is the code that is causing me problems:
/// <summary>
/// Initialize the reader.
/// </summary>
///
public override bool InitReader()
{
Logger.Instance.Debug("InitReader");
bool result = false;
// Logger.Instance.AddToDebuggerLog("Symbol.InitReader");
// If reader is already present then fail initialize
if (this._MyReader != null)
{
return false;
}
try
{
// Create new reader, first available reader will be used.
this._MyReader = new Symbol.Barcode.Reader();
// Create reader data
this._MyReaderData = new Symbol.Barcode.ReaderData(
Symbol.Barcode.ReaderDataTypes.Text,
Symbol.Barcode.ReaderDataLengths.MaximumLabel);
// Enable reader, with wait cursor
this._MyReader.Actions.Enable();
if ((GetDeviceType() != DeviceTypes.SymbolMC3070) && (GetDeviceType() != DeviceTypes.SymbolMC3090BT))
{
this._MyReader.Parameters.Feedback.Success.BeepTime = 0;
}
else
{
this._MyReader.Parameters.Feedback.Success.BeepTime = 50;
}
SetScannerDecoderTypeToUseWithScanSys();
result = true;
}
catch (Exception ex)
{
// Something has gone wrong Initializing barcode reader etc
// Log Exception
Logger.Instance.Exception("InitReader", ex);
// Ensure reader is Disposed
if (_MyReader != null)
{
try
{
_MyReader.Dispose();
}
catch
{
// Just incase something goes wrong
Logger.Instance.Error("Error Disposing MyReader in InitReader Exception");
}
_MyReader = null;
}
// Ensure ReaderData is Disposed
if (_MyReaderData != null)
{
try
{
_MyReaderData.Dispose();
}
catch
{
// Just incase something goes wrong
Logger.Instance.Error("Error Disposing MyReaderData in InitReader Exception");
}
_MyReaderData = null;
}
// null the EventHandler
_MyEventHandler = null;
}
return result;
}
My problem is that when the above method is called, the following line produces an exception error:
this._MyReader.Actions.Enable();
The exception is "OperationFailureException" and the error message mentions "Get all supported attributes failed : E_SCN_INVALIDIOCTRL"
Now the strange thing is that I am able to actually use the scanner on the device correctly, so I can scan barcodes and read the data even with this exception but the fact that it is happening concerns me so I am trying to prevent it.
Does anyone have any idea why I am getting the exception or any suggestions of things I can try?
This is a "handled" exception in the Symbol library. Just turn off the breakpoint for thrown exception-- Ctrl-Alt-E, in the row "Common Language Runtime Exceptions" uncheck the box under "Thrown". Unfortunately if you're trying to debug an exception that isn't working correctly, you just gotta keep pressing play every time this exception comes up.
I haven't found a way to make it stop throwing the exception though... I'd really like to be able to turn off whatever feature is failing.
I can't work out how to resume an interrupted upload in V3 of the C# YouTube API.
My existing code uses V1 and works fine but I'm switching to V3.
If I call UploadAsync() without changing anything, it starts from the beginning. Using Fiddler, I can see the protocol given here is not followed and the upload restarts.
I've tried setting the position within the stream as per V1 but there is no ResumeAsync() method available.
The Python example uses NextChunk but the SendNextChunk method is protected and not available in C#.
In the code below, both UploadVideo() and Resume() work fine if I leave them to completion but the entire video is uploaded instead of just the remaining parts.
How do I resume an interrupted upload using google.apis.youtube.v3?
Here is the C# code I have tried so far.
private ResumableUpload<Video> UploadVideo(
YouTubeService youTubeService, Video video, Stream stream, UserCredential userCredentials)
{
var resumableUpload = youTubeService.Videos.Insert(video,
"snippet,status,contentDetails", stream, "video/*");
resumableUpload.OauthToken = userCredentials.Token.AccessToken;
resumableUpload.ChunkSize = 256 * 1024;
resumableUpload.ProgressChanged += resumableUpload_ProgressChanged;
resumableUpload.ResponseReceived += resumableUpload_ResponseReceived;
resumableUpload.UploadAsync();
return resumableUpload;
}
private void Resume(ResumableUpload<Video> resumableUpload)
{
//I tried seeking like V1 but it doesn't work
//if (resumableUpload.ContentStream.CanSeek)
// resumableUpload.ContentStream.Seek(resumableUpload.ContentStream.Position, SeekOrigin.Begin);
resumableUpload.UploadAsync(); // <----This restarts the upload
}
void resumableUpload_ResponseReceived(Video obj)
{
Debug.WriteLine("Video status: {0}", obj.Status.UploadStatus);
}
void resumableUpload_ProgressChanged(IUploadProgress obj)
{
Debug.WriteLine("Position: {0}", (resumableUploadTest == null) ? 0 : resumableUploadTest.ContentStream.Position);
Debug.WriteLine("Status: {0}", obj.Status);
Debug.WriteLine("Bytes sent: {0}", obj.BytesSent);
}
private void button2_Click(object sender, EventArgs e)
{
Resume(resumableUploadTest);
}
Any solution/suggestion/demo or a link to the "google.apis.youtube.v3" source code will be very helpful.
Thanks in Advance !
EDIT: New information
I'm still working on this and I believe the API simply isn't finished. Either that or I'm missing something simple.
I still can't find the "google.apis.youtube.v3" source code so I downloaded the latest "google-api-dotnet-client" source code. This contains the ResumableUpload class used by the YouTube API.
I managed to successfully continue an upload by skipping the first four lines of code in the UploadAsync() method. I created a new method called ResumeAsync(), a copy of UploadAsync() with the first four lines of initialization code removed. Everything worked and the upload resumed from where it was and completed.
I'd rather not be changing code in the API so if anyone knows how I should be using this, let me know.
I'll keep plugging away and see if I can work it out.
This is the original UploadAsync() method and my ResumeAsync() hack.
public async Task<IUploadProgress> UploadAsync(CancellationToken cancellationToken)
{
try
{
BytesServerReceived = 0;
UpdateProgress(new ResumableUploadProgress(UploadStatus.Starting, 0));
// Check if the stream length is known.
StreamLength = ContentStream.CanSeek ? ContentStream.Length : UnknownSize;
UploadUri = await InitializeUpload(cancellationToken).ConfigureAwait(false);
Logger.Debug("MediaUpload[{0}] - Start uploading...", UploadUri);
using (var callback = new ServerErrorCallback(this))
{
while (!await SendNextChunkAsync(ContentStream, cancellationToken).ConfigureAwait(false))
{
UpdateProgress(new ResumableUploadProgress(UploadStatus.Uploading, BytesServerReceived));
}
UpdateProgress(new ResumableUploadProgress(UploadStatus.Completed, BytesServerReceived));
}
}
catch (TaskCanceledException ex)
{
Logger.Error(ex, "MediaUpload[{0}] - Task was canceled", UploadUri);
UpdateProgress(new ResumableUploadProgress(ex, BytesServerReceived));
throw ex;
}
catch (Exception ex)
{
Logger.Error(ex, "MediaUpload[{0}] - Exception occurred while uploading media", UploadUri);
UpdateProgress(new ResumableUploadProgress(ex, BytesServerReceived));
}
return Progress;
}
public async Task<IUploadProgress> ResumeAsync(CancellationToken cancellationToken)
{
try
{
using (var callback = new ServerErrorCallback(this))
{
while (!await SendNextChunkAsync(ContentStream, cancellationToken).ConfigureAwait(false))
{
UpdateProgress(new ResumableUploadProgress(UploadStatus.Uploading, BytesServerReceived));
}
UpdateProgress(new ResumableUploadProgress(UploadStatus.Completed, BytesServerReceived));
}
}
catch (TaskCanceledException ex)
{
UpdateProgress(new ResumableUploadProgress(ex, BytesServerReceived));
throw ex;
}
catch (Exception ex)
{
UpdateProgress(new ResumableUploadProgress(ex, BytesServerReceived));
}
return Progress;
}
These are the Fiddler records showing the upload resuming.
After a fair bit of deliberation, I've decided to modify the API code. My solution maintains backwards compatibility.
I've documented my changes below but I don't recommend using them.
In the UploadAsync() method in the ResumableUpload Class in "Google.Apis.Upload", I replaced this code.
BytesServerReceived = 0;
UpdateProgress(new ResumableUploadProgress(UploadStatus.Starting, 0));
// Check if the stream length is known.
StreamLength = ContentStream.CanSeek ? ContentStream.Length : UnknownSize;
UploadUri = await InitializeUpload(cancellationToken).ConfigureAwait(false);
with this code
UpdateProgress(new ResumableUploadProgress(
BytesServerReceived == 0 ? UploadStatus.Starting : UploadStatus.Resuming, BytesServerReceived));
StreamLength = ContentStream.CanSeek ? ContentStream.Length : UnknownSize;
if (UploadUri == null) UploadUri = await InitializeUpload(cancellationToken).ConfigureAwait(false);
I also made the UploadUri and BytesServerReceived properties public. This allows an upload to be continued after the ResumableUpload object has been destroyed or after an application restart.
You just recreate the ResumableUpload as per normal, set these two fields and call UploadAsync() to resume an upload. Both fields need to be saved during the original upload.
public Uri UploadUri { get; set; }
public long BytesServerReceived { get; set; }
I also added "Resuming" to the UploadStatus enum in the IUploadProgress class.
public enum UploadStatus
{
/// <summary>
/// The upload has not started.
/// </summary>
NotStarted,
/// <summary>
/// The upload is initializing.
/// </summary>
Starting,
/// <summary>
/// Data is being uploaded.
/// </summary>
Uploading,
/// <summary>
/// Upload is being resumed.
/// </summary>
Resuming,
/// <summary>
/// The upload completed successfully.
/// </summary>
Completed,
/// <summary>
/// The upload failed.
/// </summary>
Failed
};
Nothing has changed for starting an upload.
Provided the ResumableUpload Oject and streams have not been destroyed, call UploadAsync() again to resume an interrupted upload.
If they have been destroyed, create new objects and set the UploadUri and BytesServerReceived properties. These two properties can be saved during the original upload. The video details and content stream can be configured as per normal.
These few changes allow an upload to be resumed even after restarting your application or rebooting. I'm not sure how long before an upload expires but I'll report back when I've done some more testing with my real application.
Just for completeness, this is the test code I've been using, which happily resumes an interrupted upload after restarting the application multiple times during an upload. The only difference between resuming and restarting, is setting the UploadUri and BytesServerReceived properties.
resumableUploadTest = youTubeService.Videos.Insert(video, "snippet,status,contentDetails", fileStream, "video/*");
if (resume)
{
resumableUploadTest.UploadUri = Settings.Default.UploadUri;
resumableUploadTest.BytesServerReceived = Settings.Default.BytesServerReceived;
}
resumableUploadTest.ChunkSize = ResumableUpload<Video>.MinimumChunkSize;
resumableUploadTest.ProgressChanged += resumableUpload_ProgressChanged;
resumableUploadTest.UploadAsync();
I hope this helps someone. It took me much longer than expected to work it out and I'm still hoping I've missed something simple. I messed around for ages trying to add my own error handlers but the API does all that for you. The API does recover from minor short hiccups but not from an application restart, reboot or prolonged outage.
Cheers. Mick.
This issue has been resolved in version "1.8.0.960-rc" of the Google.Apis.YouTube.v3 Client Library.
They've added a new method called ResumeAsync and it works fine. I wish I'd known they were working on it.
One minor issue I needed to resolve was resuming an upload after restarting the application or rebooting. The current api does not allow for this but two minor changes resolved the issue.
I added a new signature for the ResumeAsync method, which accepts and sets the original UploadUri. The StreamLength property needs to be initialised to avoid an overflow error.
public Task<IUploadProgress> ResumeAsync(Uri uploadUri, CancellationToken cancellationToken)
{
UploadUri = uploadUri;
StreamLength = ContentStream.CanSeek ? ContentStream.Length : UnknownSize;
return ResumeAsync(cancellationToken);
}
I also exposed the getter for UploadUri so it can be saved from the calling application.
public Uri UploadUri { get; private set; }
I've managed to get this to work using reflection and avoided the need to modify the API at all. For completeness, I'll document the process but it isn't recommended. Setting private properties in the resumable upload object is not a great idea.
When your resumeable upload object has been destroyed after an application restart or reboot, you can still resume an upload using version "1.8.0.960-rc" of the Google.Apis.YouTube.v3 Client Library.
private static void SetPrivateProperty<T>(Object obj, string propertyName, object value)
{
var propertyInfo = typeof(T).GetProperty(propertyName, BindingFlags.NonPublic | BindingFlags.Instance);
if (propertyInfo == null) return;
propertyInfo.SetValue(obj, value, null);
}
private static object GetPrivateProperty<T>(Object obj, string propertyName)
{
if (obj == null) return null;
var propertyInfo = typeof(T).GetProperty(propertyName, BindingFlags.NonPublic | BindingFlags.Instance);
return propertyInfo == null ? null : propertyInfo.GetValue(obj, null);
}
You need to save the UploadUri during the ProgressChanged event.
Upload.ResumeUri = GetPrivateProperty<ResumableUpload<Video>>(InsertMediaUpload, "UploadUri") as Uri;
You need to set the UploadUri and StreamLength before calling ResumeAsync.
private const long UnknownSize = -1;
SetPrivateProperty<ResumableUpload<Video>>(InsertMediaUpload, "UploadUri", Upload.ResumeUri);
SetPrivateProperty<ResumableUpload<Video>>(InsertMediaUpload, "StreamLength", fileStream.CanSeek ? fileStream.Length : Constants.UnknownSize);
Task = InsertMediaUpload.ResumeAsync(CancellationTokenSource.Token);
I have an optimistic concurrency method from which I need to return a value. I am getting an error indicating the return variable is not in scope.
private static string GenerateCustomerId(string contextPath)
{
var retryMaxCount = 3; // maximum number of attempts
var cycles = 0; // current attempt
Exception exception = null; // inner exception storage
while (cycles++ < retryMaxCount) // cycle control
{
try
{
Content sequenceContent = Content.Load(contextPath);
int currentSequence;
int.TryParse(sequenceContent["LastSequenceNo"].ToString(), out currentSequence);
currentSequence++;
string currentDate = DateTime.Now.ToString("ddMMyyyy");
string customerID = string.Format("{0}{1}", currentDate, currentSequence);
//Save back to content with new update
sequenceContent["LastSequenceNo"] = currentSequence.ToString();
sequenceContent["LastCustomerID"] = customerID;
sequenceContent.Save();
}
catch (NodeIsOutOfDateException e)
{
exception = e; // storing the exception temporarily
}
return customerID; //"**Customer ID does not exist in current context**"
}
// rethrow if needed
if (exception != null)
throw new ApplicationException("Node is out of date after 3 attempts.", exception);
}
How can I return the value of CustomerID?
Just move the return statement into the try block - and then add an extra throw statement at the very end of the method; if you ever reach the end of the method without an exception, that indicates something very strange going on. Or you could just make the final throw unconditional, of course:
private static string GenerateCustomerId(string contextPath)
{
var retryMaxCount = 3; // maximum number of attempts
Exception exception = null; // inner exception storage
for (int cycles = 0; cycles < retryMaxCount; cycles++)
{
try
{
...
// If we get to the end of the try block, we're fine
return customerID;
}
catch (NodeIsOutOfDateException e)
{
exception = e; // storing the exception temporarily
}
}
throw new ApplicationException(
"Node is out of date after " + retryMaxCount + " attempts.", exception);
}
As an aside, I'd personally avoid ApplicationException - I'd either just rethrow the original exception, or create a dedicated RetryCountExceeded exception or something similar. ApplicationException was basically a mistake on Microsoft's part, IMO.
(Also note that I've converted your while loop into a for loop for simplicity. I would certainly find the for loop easier to read and understand, and I suspect most other developers would feel the same way. I'd consider making retryMaxCount a constant in your class rather than a local variable, too.)