0

I am developing an UWP app where you can download videos from YouTube and convert them to a mp3 file. My problem here is that the process of writing the bytes of the downloaded video to the created .mp4 file freezes the application although it is marked as async and all the necessary function calls are marked with the await keyword.

The following function has been assigned to the Click attribute of the button:

private async void DownloadMp3File(object sender, RoutedEventArgs e)
{
    string url = youtubeUrl.Text;

    if (!string.IsNullOrEmpty(url) && url.Contains("youtube"))
    {
        string savePath = Environment.GetFolderPath(Environment.SpecialFolder.MyMusic);

        StorageFolder storageFolder = KnownFolders.MusicLibrary;
    
        var youTube = YouTube.Default; // starting point for YouTube actions
        var video = await youTube.GetVideoAsync(url); // gets a Video object with info about the video

        string videoPath = Path.Combine(savePath, video.FullName);
        string mp3Path = Path.Combine(savePath, videoPath.Replace(".mp4", ".mp3"));

        StorageFile videoFile = await storageFolder.CreateFileAsync(Path.GetFileName(videoPath), CreationCollisionOption.ReplaceExisting);
        StorageFile mp3File = await storageFolder.CreateFileAsync(Path.GetFileName(mp3Path), CreationCollisionOption.ReplaceExisting);

        await WriteBytesIntoVideoFile(videoFile, video).ConfigureAwait(false);
        await ConvertMp4ToMp3(videoFile, mp3File);
        await videoFile.DeleteAsync();
    }
}        

During debugging I managed to find out where the app would freeze. It freezes exactly during the time of writing the bytes of the YouTube video into the mp4 file. The function WriteBytesIntoVideoFile writes the bytes of the downloaded video into the created video file. before this implementation I used FileIO.WriteBytesAsync but it is the same behaviour:

public async Task WriteBytesIntoVideoFile(StorageFile videoFile, YouTubeVideo downloadedVideo)
{
    //await FileIO.WriteBytesAsync(videoFile, downloadedVideo.GetBytes());

    var outputStream = await videoFile.OpenAsync(FileAccessMode.ReadWrite);
    using (var dataWriter = new DataWriter(outputStream))
    {
        foreach (var byt in downloadedVideo.GetBytes())
        {
            dataWriter.WriteByte(byt);
        }
        await dataWriter.StoreAsync();
        await outputStream.FlushAsync();
    }
}

Finally the convert method which converts the mp4 to a mp3. It uses the MediaTranscoder to achieve this.

public async Task ConvertMp4ToMp3(IStorageFile videoFile, IStorageFile mp3File)
{
    MediaEncodingProfile profile = MediaEncodingProfile.CreateMp3(AudioEncodingQuality.Auto);
    MediaTranscoder transcoder = new MediaTranscoder();

    PrepareTranscodeResult prepareOp = await transcoder.PrepareFileTranscodeAsync(videoFile, mp3File, profile);

    if (prepareOp.CanTranscode)
    {
        await prepareOp.TranscodeAsync();

        //transcodeOp.Progress +=
        //    new AsyncActionProgressHandler<double>(TranscodeProgress);
        //transcodeOp.Completed +=
        //    new AsyncActionWithProgressCompletedHandler<double>(TranscodeComplete);
    }
    else
    {
        switch (prepareOp.FailureReason)
        {
            case TranscodeFailureReason.CodecNotFound:
                System.Diagnostics.Debug.WriteLine("Codec not found.");
                break;
            case TranscodeFailureReason.InvalidProfile:
                System.Diagnostics.Debug.WriteLine("Invalid profile.");
                break;
            default:
                System.Diagnostics.Debug.WriteLine("Unknown failure.");
                break;
        }
    }
}       

Could anyone explain to me why the app freezes during the process of writing the bytes of the downloaded video to the mp4 file? Any help is appreciated.

Peter Csala
  • 17,736
  • 16
  • 35
  • 75
Florent
  • 111
  • 10
  • Related: [Why File.ReadAllLinesAsync() blocks the UI thread?](https://stackoverflow.com/questions/63217657/why-file-readalllinesasync-blocks-the-ui-thread) – Theodor Zoulias Jun 19 '21 at 15:51
  • The awaits will cause your application to block until the async methods complete. I think you could use `Task.ContinueWith()` so the event handler can return – user700390 Jun 19 '21 at 16:16
  • @user700390 the `await` doesn't block. The whole point of inventing `await` was to simplify asynchronous programming, and asynchronous means non-blocking. As for the `ContinueWith`, it is a [primitive](https://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html) method that has become largely obsolete after the introduction of async/await. – Theodor Zoulias Jun 19 '21 at 16:37
  • `foreach (var byt in downloadedVideo.GetBytes()) {dataWriter.WriteByte(byt); }` is going to run a long loop on your UI thread. Instead, move this work to a threadpool thread and await the result from the UI thread. – Raymond Chen Jun 19 '21 at 16:40
  • @TheodorZoulias I believe if you await on the UI thread with long running operation, the application will stop responding until the await call completes. I have seen this in Windows Forms programming, similar to as described here https://stackoverflow.com/questions/36563488/await-still-blocks-current-ui-thread – user700390 Jun 19 '21 at 16:41
  • @user700390 in [the example](https://stackoverflow.com/questions/68048311/problem-with-asynchronous-function-when-writing-bytes-into-a-file) you linked it's not the `await` that blocks, it's the asynchronous method that creates the awaitable that blocks. That method is badly implemented, and blocks the current thread instead of returning immediately an incomplete `Task` as it should according to [the guidelines](https://learn.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/task-based-asynchronous-pattern-tap#initiating-an-asynchronous-operation). – Theodor Zoulias Jun 19 '21 at 16:46
  • Have you already run the profiler? I would bet that issue is somewhere within downloadedVideo.GetBytes(). – Oleg Mikhailov Jun 19 '21 at 19:36
  • Someone replied here earlier but removed it. The person suggested to change youtube.GetVideoAsync(url) to youtube.GetVideoAsync(url).ConfigureAwait(false). After I did that the problem got solved. I researched about this a bit and found out that you can avoid possible deadlocks with this ConfigureAwait function – Florent Jun 19 '21 at 20:39
  • 1
    Florent don't use `ConfigureAwait` in application code. This method is used mainly in library code. Using it as a way to offload work to background threads in application code is neither reliable, nor expressive regarding the code's intention. The correct way to offload work from the UI thread, is to use the `Task.Run` method. – Theodor Zoulias Jun 20 '21 at 01:35
  • When I change the line `var video = await youTube.GetVideoAsync(url)` to `var video = await Task.Run(() => youTube.GetVideoAsync(url));` then the freeze problem still persists. Using ConfigureAwait solved it pretty much. My question here would be why not to use it in Application Code? – Florent Jun 20 '21 at 09:28
  • Could you please convert your comment as an answer? So that you could mark your own answer, for that, more and more members could know this issue and get a favor quickly. – dear_vv Jun 21 '21 at 07:07
  • Florent do you understand why adding the `.ConfigureAwait(false)` solved the freezing problem? If you don't, then what have you learned here is that every time you have a freezing problem you can just sprinkle your code with `.ConfigureAwait(false)` here and there, hoping that somehow the problem will be fixed. I guess that's one way to write software. But it's not a robust one, and not one to be recommended in general. – Theodor Zoulias Jun 21 '21 at 09:41
  • 1
    Task.Run(() => youTube.GetVideoAsync(url)); does not help because problem is not at this line, it is below. ConfigureAwait(false) sometimes (but not always!) schedules code AFTER it to background thread. – Oleg Mikhailov Jun 21 '21 at 16:19

0 Answers0