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);
Related
I will try to tell my problem in as simple words as possible.
In my UWP app, I am loading the data async wise on my Mainpage.xaml.cs`
public MainPage()
{
this.InitializeComponent();
LoadVideoLibrary();
}
private async void LoadVideoLibrary()
{
FoldersData = new List<FolderData>();
var folders = (await Windows.Storage.StorageLibrary.GetLibraryAsync
(Windows.Storage.KnownLibraryId.Videos)).Folders;
foreach (var folder in folders)
{
var files = (await folder.GetFilesAsync(Windows.Storage.Search.CommonFileQuery.OrderByDate)).ToList();
FoldersData.Add(new FolderData { files = files, foldername = folder.DisplayName, folderid = folder.FolderRelativeId });
}
}
so this is the code where I am loading up a List of FolderData objects.
There in my other page Library.xaml.cs I am using that data to load up my gridview with binding data.
protected override void OnNavigatedTo(NavigationEventArgs e)
{
try
{
LoadLibraryMenuGrid();
}
catch { }
}
private async void LoadLibraryMenuGrid()
{
MenuGridItems = new ObservableCollection<MenuItemModel>();
var data = MainPage.FoldersData;
foreach (var folder in data)
{
var image = new BitmapImage();
if (folder.files.Count == 0)
{
image.UriSource = new Uri("ms-appx:///Assets/StoreLogo.png");
}
else
{
for (int i = 0; i < folder.files.Count; i++)
{
var thumb = (await folder.files[i].GetThumbnailAsync(Windows.Storage.FileProperties.ThumbnailMode.VideosView));
if (thumb != null) { await image.SetSourceAsync(thumb); break; }
}
}
MenuGridItems.Add(new MenuItemModel
{
numberofvideos = folder.files.Count.ToString(),
folder = folder.foldername,
folderid = folder.folderid,
image = image
});
}
GridHeader = "Library";
}
the problem I am facing is that when i launch my application, wait for a few seconds and then i navigate to my library page, all data loads up properly.
but when i try to navigate to library page instantly after launching the app, it gives an exception that
"collection was modified so it cannot be iterated"
I used the breakpoint and i came to know that if i give it a few seconds the List Folder Data is already loaded properly asyncornously, but when i dnt give it a few seconds, that async method is on half way of loading the data so it causes exception, how can i handle this async situation? thanks
What you need is a way to wait for data to arrive. How you fit that in with the rest of the application (e.g. MVVM or not) is a different story, and not important right now. Don't overcomplicate things. For example, you only need an ObservableCollection if you expect the data to change while the user it looking at it.
Anyway, you need to wait. So how do you wait for that data to arrive?
Use a static class that can be reached from everywhere. In there put a method to get your data. Make sure it returns a task that you cache for future calls. For example:
internal class Data { /* whatever */ }
internal static class DataLoader
{
private static Task<Data> loaderTask;
public static Task<Data> LoadDataAsync(bool refresh = false)
{
if (refresh || loaderTask == null)
{
loaderTask = LoadDataCoreAsync();
}
return loaderTask;
}
private static async Task<Data> LoadDataCoreAsync()
{
// your actual logic goes here
}
}
With this, you can start the download as soon as you start the application.
await DataLoader.LoadDataAsync();
When you need the data in that other screen, just call that method again. It will not download the data again (unless you set refresh is true), but will simply wait for the work that you started earlier to finish, if it is not finished yet.
I get that you don't have enough experience.There are multiple issues and no solution the way you are loading the data.
What you need is a Service that can give you ObservableCollection of FolderData. I think MVVM might be out of bounds at this instance unless you are willing to spend a few hours on it. Though MVVM will make things lot easier in this instance.
The main issue at hand is this
You are using foreach to iterate the folders and the FolderData list. Foreach cannot continue if the underlying collection changes.
Firstly you need to start using a for loop as opposed to foreach. 2ndly add a state which denotes whether loading has finished or not. Finally use observable data source. In my early days I used to create static properties in App.xaml.cs and I used to use them to share / observe other data.
I'm working with .NET 3.5 with a simple handler for http requests. Right now, on each http request my handler opens a tcp connection with 3 remote servers in order to receive some information from them. Then closes the sockets and writes the server status back to Context.Response.
However, I would prefer to have a separate object that every 5 minutes connects to the remote servers via tcp, gets the information and keeps it. So the HttpRequest, on each request would be much faster just asking this object for the information.
So my questions here are, how to keep a shared global object in memory all the time that can also "wake" an do those tcp connections even when no http requests are coming and have the object accesible to the http request handler.
A service may be overkill for this.
You can create a global object in your application start and have it create a background thread that does the query every 5 minutes. Take the response (or what you process from the response) and put it into a separate class, creating a new instance of that class with each response, and use System.Threading.Interlocked.Exchange to replace a static instance each time the response is retrieved. When you want to look the the response, simply copy a reference the static instance to a stack reference and you will have the most recent data.
Keep in mind, however, that ASP.NET will kill your application whenever there are no requests for a certain amount of time (idle time), so your application will stop and restart, causing your global object to get destroyed and recreated.
You may read elsewhere that you can't or shouldn't do background stuff in ASP.NET, but that's not true--you just have to understand the implications. I have similar code to the following example working on an ASP.NET site that handles over 1000 req/sec peak (across multiple servers).
For example, in global.asax.cs:
public class BackgroundResult
{
public string Response; // for simplicity, just use a public field for this example--for a real implementation, public fields are probably bad
}
class BackgroundQuery
{
private BackgroundResult _result; // interlocked
private readonly Thread _thread;
public BackgroundQuery()
{
_thread = new Thread(new ThreadStart(BackgroundThread));
_thread.IsBackground = true; // allow the application to shut down without errors even while this thread is still running
_thread.Name = "Background Query Thread";
_thread.Start();
// maybe you want to get the first result here immediately?? Otherwise, the first result may not be available for a bit
}
/// <summary>
/// Gets the latest result. Note that the result could change at any time, so do expect to reference this directly and get the same object back every time--for example, if you write code like: if (LatestResult.IsFoo) { LatestResult.Bar }, the object returned to check IsFoo could be different from the one used to get the Bar property.
/// </summary>
public BackgroundResult LatestResult { get { return _result; } }
private void BackgroundThread()
{
try
{
while (true)
{
try
{
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create("http://example.com/samplepath?query=query");
request.Method = "GET";
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
using (StreamReader reader = new StreamReader(response.GetResponseStream(), System.Text.Encoding.UTF8))
{
// get what I need here (just the entire contents as a string for this example)
string result = reader.ReadToEnd();
// put it into the results
BackgroundResult backgroundResult = new BackgroundResult { Response = result };
System.Threading.Interlocked.Exchange(ref _result, backgroundResult);
}
}
}
catch (Exception ex)
{
// the request failed--cath here and notify us somehow, but keep looping
System.Diagnostics.Trace.WriteLine("Exception doing background web request:" + ex.ToString());
}
// wait for five minutes before we query again. Note that this is five minutes between the END of one request and the start of another--if you want 5 minutes between the START of each request, this will need to change a little.
System.Threading.Thread.Sleep(5 * 60 * 1000);
}
}
catch (Exception ex)
{
// we need to get notified of this error here somehow by logging it or something...
System.Diagnostics.Trace.WriteLine("Error in BackgroundQuery.BackgroundThread:" + ex.ToString());
}
}
}
private static BackgroundQuery _BackgroundQuerier; // set only during application startup
protected void Application_Start(object sender, EventArgs e)
{
// other initialization here...
_BackgroundQuerier = new BackgroundQuery();
// get the value here (it may or may not be set quite yet at this point)
BackgroundResult result = _BackgroundQuerier.LatestResult;
// other initialization here...
}
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();
}
}
So this application runs seamlessly in Visual Studio, but I have created an installer for the program in which the error is encountered. I think I have pinned down what the issue is. When a POST is received it is handled which kicks off a separate decoupled process which eventually gets aborted from the webpage disposing/closing.
The program flow is such
POST received context.Request.HttpMethod == "POST",
pertinent xml info extracted and written to disk,
csfireEyeHandler.DonJobOnLastIp(),
a monitor running in the background picks up on the file creation event `void OnChanged' and starts running services based on the XML doc
FileAdded --> readerRef.ReadInServices(e.FullPath, false).
The problem is after the POST is handled it causes the services to abort with the ThreadAbortException. If a delay is placed after handler.ProcessRequest(context) the services finish, I presume because the page still open. I cannot figure out how to properly handle this situation, its terribly difficult to debug because I cannot get the error to occur in VS.
public partial class fireEye : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
HttpContext context = Context;
fireEyeHandler handler = new fireEyeHandler();
handler.ProcessRequest(context);
}
}
public class fireEyeHandler : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
if (context.Request.HttpMethod == "POST")
{
var extension = context.Request.Url.AbsolutePath.Split('/')[2].ToLower();
var stream = context.Request.InputStream;
var buffer = new byte[stream.Length];
stream.Read(buffer, 0, buffer.Length);
var xml = Encoding.UTF8.GetString(buffer);
FileManage.WriteToFile(xml, #"C:\ECC_output\fireEye.xml");
var csfireEyeHandler = new FireEyeService { config = extension + ".config" };
csfireEyeHandler.Load();
csfireEyeHandler.DonJobOnLastIp();
context.Response.StatusCode = 202;
}
}
public bool IsReusable
{
get { return false; }
}
}
public class Monitor
{
bool monitorIsActive;
readonly XmlRead readerRef; // Reference to the xml reader
readonly FileSystemWatcher watch;
public bool monitorRunning;
public Monitor(XmlRead reader)
{
watch = new FileSystemWatcher();
readerRef = reader;
try
{
watch.Path = #"C:\ECC_temp"; //directory to monitor
}
catch (ArgumentException ex)
{
Report.LogLine (ex.Message);
return;
}
watch.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
watch.Filter = "*.xml";
monitorIsActive = true;
watch.Created += OnChanged;
watch.Deleted += OnChanged;
watch.Renamed += OnRenamed;
watch.EnableRaisingEvents = true;
}
/// <summary>
/// Toggles on/off if a directory is being monitored
/// </summary>
public void ToggleMonitor()
{
monitorIsActive = !monitorIsActive;
var monitorState = monitorIsActive ? "on" : "false";
Report.LogLine ("Monitor is " + monitorState);
}
/// <summary>
/// File has been added to the directory
/// </summary>
public bool FileAdded(FileSystemEventArgs e, XmlDocument xmlDocument)
{
try
{
var date = string.Format ("<br>\r\n**********************Report {0:yyyy MM-dd hh:mm tt}**********************", DateTime.Now);
Report.LogLine(date);
readerRef.Validate(e.FullPath, false);
readerRef.ReadInServices(e.FullPath, false);
Report.CreateReport();
}
catch (Exception exception)
{
Report.LogLine(exception.Message + " id:6");
Report.CreateReport();
return true;
}
return true;
}
/// <summary>
/// When a file is added, renamed or deleted, OnChanged is called and the appropriate action is taken
/// </summary>
private void OnChanged(object source, FileSystemEventArgs e)
{
monitorRunning = true;
while (true)
{
if (e.ChangeType == WatcherChangeTypes.Created || e.ChangeType == WatcherChangeTypes.Renamed)
{
var xmlDocument = new XmlDocument();
try
{
xmlDocument.Load(e.FullPath);
}
catch (IOException)
{
Thread.Sleep(100);
}
if (FileAdded(e, xmlDocument))
{
break;
}
}
}
monitorRunning = false;
}
}
Unless your application knows about this Monitor and knows how to detect that its currently handling a file change event and not return from the post, there's nothing you can do. I would suggest not coupling the web application to this Monitor (you haven't explained its purpose). I would suggest pulling it out of the web application and putting it in a Windows service of some sort. Or, explain in more detail why this Monitor is in the web application.
The first problem is that FileSystemWatcher can fire as soon as the file is created, which can often be while the ASP.NET process is still in the middle of writing the document.
I'd expect this to cause an XML read exception, or an access denied exception however, not neccessarily a ThreadAbort exception. ThreadAbort exceptions usually only happen if someone calls Thread.Abort, which you should never, ever do.
Anyway, You can get around this by using System.IO.FileStream to write your file and tell windows to lock it while it's being written to.
When you open the file to write it, specify FileShare.None. This will prevent the monitor from trying to read the file until the ASP.net process is done writing.
In your Monitor, add a retry loop with a small sleep in it when opening the file. You should get an access denied exception (which I think will be an IOException) repeatedly until the file is ready for you to read.
I can't really help any more than that without understanding what your csFireEyeHandler thing is supposed to do. Are you expecting the monitor service to finish processing the file in the middle of your ASP.net request? This is unlikely to happen.
What I'd expect the workflow to be:
ASP PAGE Monitor Process
> File Uploaded |
| |
> Begin Write to disk |
| > Try open file
| > fail to read file and retry
> Finish write to disk |
| > open file
| |
| > Begin process file
> csFireEyeHandler.Load |
> csFireEyeHandler.DonJob |
> RETURN |
|
> Finish process file (Report.CreateReport)
If in fact you need the fireEyeHandler to wait for the background service, there are several ways you can do this... but why not just process the file in the fireEyeHandler?
According to my interpretation of your question you don't want to wait until a file monitor event has occurred before you send the response to the client. You want to send 200 immediately and process in the background.
This is an anti-pattern in ASP.NET because the runtime does not guarantee that a worker process stays alive when all running HTTP requests have exited. In that sense, ASP.NET is conforming to the specification: It can abort your custom threads at any time.
It would be easier to get this right if you waited until the "service processing" is completed before sending 200 as an HTTP response.
This is about the AutoResetEvent in C#. I tried to read other answers but I could not make sense and apply to my scenario. I am not writing any threading application. Just a small application to read/validate a file and update.
So I have this requirement to write some code for reading a fixed length file, validating it and then if it is valid upload it to Database.
I got everything working until I got stuck with the AutoResetEvent. So here is what is happening. Once the data is parsed/read I validate it using Flat File Checker utility in C#. So I called the functions into my application. Here is the snippet.
private AutoResetEvent do_checks = new AutoResetEvent(false);
public bool ValidationComplete = false;
This part goes in initialization code:
this._files.Validated += new EventHandler<SchemaValidatedEventArgs>(FileSetValidated);
public bool ValidateFile()
{
try
{
RunValidation();
return true;
}
catch (Exception e)
{
log.Error("Data Validation failed because :" + e.Message);
return false;
}
}
private void RunValidation()
{
// Use Flat File Checker user interface to create Schema file.
do_checks = _files.RunChecks();
log.Debug("Validation Started");
}
This is the method that is getting called asnchronusly during the validation process:
public void FileSetValidated(Object sender, SchemaValidatedEventArgs e)
{
try
{
ValidationComplete = e.Result;
if (IsDataValid)
{
log.Debug("Data is validated and found to be valid.");
}
else
{
log.Debug("Data is validated and found to be Invalid");
}
}
finally
{
do_checks.Set();
}
}
What is happening is that even before I get any value set into ValidationComplete the code is checked for Validation complete and because it is set by default to false, it returns false. The code in the FileSetValidated gets executed after that so the database update never happens.
The reason is that I cannot change the code because the Flat File Checker only accepts an AutoResetEvent as a return variable in RunChecks method.
******Here is what I did now*******
private AutoResetEvent do_checks;
public bool ValidateFile()
{
try
{
string extFilePath = surveyFile.ExtFilePath;
File.Copy(extFilePath, localTempFolder + "ExtractFile.Dat");
RunValidation();
if (!do_checks.WaitOne(TimeSpan.FromSeconds(30))) {
// throw new ApplicationException("Validation took more than expected!");
}
return true;
}
catch (Exception e)
{
log.Error("Data Validation failed because :" + e.Message);
return false;
}
}
private void RunValidation()
{
// Use Flat File Checker user interface to create Schema file.
do_checks = _files.RunChecks();
do_checks.WaitOne();
log.Debug("Validation Started");
}
Also I moved the part where data about validation gets passed on towards the beginning of the event handler so atleast that part gets executed. This helped but I am not sure if it is correct.
I have never worked with that lib, so I just downloaded it and looked into the code.
First of all, as "500 - Internal Server Error" already mentioned, it seems that part of the code is missing, at least "try" in the FileSetValidated method. I don't see any place where you are waiting for the event via WaitOne.
You don't need to create do_checks by yourself, because _files.RunChecks() creates AutoResetEven for this particular file's processing. So if you are using the same field for that event - you will get issue if you will need to process few files at the same time. So keep separate event for each file, in any case I don't see reason to keep that references as members if you don't want to stop processing in the middle (if you will call do_checks.Set() during processing, it will cancel processing without finishing it).
As I see in the lib code, you should not call do_checks.Set() in the FileSetValidated method, because it will be set, once processing will be done, so you can just write:
var do_checks = _files.RunChecks();
do_checks.WaitOne();
Feel free to share if that helped.
UPDATE:
I am not able to check that lib now to undestand why do_checks is set after starting processing, but I can suggest you to use your initial code with next RunValidation method:
private void RunValidation()
{
do_checks.Reset(); //reset state
_files.RunChecks(); //don't store event from the lib
log.Debug("Validation Started");
do_checks.WaitOne(); //Wait for FileSetValidated to set this event
}
Before exiting the ValidateFile function you need to wait for the validation to complete (wait on the AutoResetEvent) and return the validation result.
Try something like this:
public bool ValidateFile()
{
//try
{
RunValidation();
//Allocate enough time for the validation to occur but make sure
// the application doesn't block if the _files.Validated event doesn't get fired
if(!do_checks.WaitOne(TimeSpan.FromSeconds(10)))
{
throw ApplicationException("Validation took more than expected!");
}
return ValidationComplete;
}
//I would not catch the exception since having an error doesn't mean that the file
//is invalid. Catch it upper in the call stack and inform the user that the validation
//could not be performed because of the error
//catch (Exception e)
//{
// log.Error("Data Validation failed because :" + e.Message);
// return false;
//}
}