wiz0u/WTelegramClient

Call to DownloadFileAsync() not returning when called from new thread

call-eax opened this issue · 3 comments

Downloading a Photo from the UI thread in a Windows Forms Application works.
When using Task.Run to move the download to a background task in order to keep the UI repsonsive, a call to
DownloadFileAsync(Photo, Stream) fails to return.

Example:

private async Task SearchAndDisplayAsync()
{
...
...

    else if (msg.media is MessageMediaPhoto)
    {
      MessageMediaPhoto photo = (MessageMediaPhoto)msg.media;
      Photo thePhoto = (Photo)photo.photo;
      var mi = new MessageItem(thePhoto);
      Image largeImg, smallImg;
      using (MemoryStream memStream = new MemoryStream())
      {
       await tgAPI.DownloadAsync(thePhoto, memStream, thePhoto.sizes[0], null);
        smallImg = Image.FromStream(memStream);
      }
      using (MemoryStream memStream = new MemoryStream())
      {
        await tgAPI.DownloadAsync(thePhoto, memStream, thePhoto.sizes[thePhoto.sizes.Length - 1], null);
        largeImg = Image.FromStream(memStream);
      }
...
...
}

The code above is trying to download a small photo and a large photo.
When called from the UI thread of a Windows Forms application (await SearchAndDisplayAsync();), both calls succeed and both images are downloaded.
When using await Task.Run(() => SearchAndDisplayAsync()); to run the method on a background thread, only the first download succeeds (the smallest photo). The second call just hangs and does not return.
If I reverse the order of the calls and download the largest file first, the call does also not return.

tgApi.DownloadAsync() is a wrapper around WTelegramClient's DownloadFileAsync():

public async Task DownloadAsync(Photo photo, Stream file, PhotoSizeBase thumbSize, Client.ProgressCallback progress)
{
    await Client.DownloadFileAsync(photo, file, thumbSize, progress);
}

Excerpt from the log:

2023-12-08 16:00:53 [D]             → Upload_GetFile                         #2656
2023-12-08 16:00:53 [D]             → MsgsAck                               
2023-12-08 16:00:54 [D] 2>Receiving RpcResult                                2023-12-08 19:00:54Z
2023-12-08 16:00:54 [D]              → Upload_File                           #2656
2023-12-08 16:01:29 [D] 2>Receiving Updates                                  2023-12-08 19:01:29Z  
2023-12-08 16:02:04 [D] 2>Receiving Updates                                  2023-12-08 19:02:04Z  
2023-12-08 16:03:00 [D] 2>Receiving Updates                                  2023-12-08 19:03:00Z  
2023-12-08 16:04:03 [I] 2>Disposing the client

System: Windows 10 pro 22H2
WTelegramClient: 3.6.1

Created a reproduction solution.
The gist of the issue seems to be that if you create a usercontrol within (here) a foreach loop in which large images are downloaded, the download eventually will hang.
The solution appended is as minimal as I could make it. It should automatically download all referenced libraries upon first build.

Please make sure to be subscribed to Telegram's news channel: https://t.me/telegram
The program is hardcoded to search in this channel.
The App will write its session storage to %appdata%\TelegramApp\sessionStoreTGD
Logs are redirected into the output build directory as WTelegram.log

Steps to reproduce:

  1. Load solution. Breakpoint is already set on the DownloadAsync method (line 120 in TDForm.cs)
  2. Edit TelegramApi.cs and insert valid api_id and api_hash in Config() method.
  3. Add your telephone number in TDForm.cs in the GetTelephone() method.
  4. Build
  5. Run (Debug)
  6. Press Login button. The first time, the app should ask you for the security code (popup window) and, if configured, for your 2FA password.
  7. Enter news in the search box and press enter. The program will then search in Telegram's news channel.
  8. The program will eventually stop at the breakpoint (first iteration).
  9. Press F5 to continue debugging to arrive again at the breakpoint.
  10. Step over it and the program should now hang.

TelegramAppBugReproduction.zip

I've spent an unreasonable amount of time debugging your code and found the root cause of the problem is in your code, not in the library.
You're mixing UI code & threadpool-tasks in your code. It's often a recipe for disaster.

You have two solutions:

  • either remove all the Task.Run() from your program (WTC runs fine on WinForms task synchronization context)
  • or make sure ALL code (from those Task.Run tasks) accessing WinForms UI are protected inside a call to Form.Invoke((Action)(() => { ... })), even the simple creation of a UserControl instance.

For the experts, the advanced diagnostic is that the UserControl instance contains a PictureBox, and its instanciation force the reinstallation of WinForms synchronizationcontext as Current, when it should stay null in background tasks.