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