Investigate AddPackageAsync hangs
Determine whether issue still repros with recent version and investigate:
https://github.com/dotnet/wpf/issues/4097
This is easily reproducible. Just create an empty WPF app with .net 8.0 and call
await packageManager.AddPackageAsync(...)
it is not reproduced every time but usually on every 5 calls, hangs at least 1 time. It is only reproducible in Release
Calling the method synchrously packageManager.AddPackageAsync(...).GetWaiter().GetResult()
is a workaround for this issue but we noticed the installation of the same package is noticeable slower using the synchronous call.
This is not reproduced in WinUI3 app (or it is possible to be much harder to be reproduced)
We found that calling Task.Run(() => packageManager.AddPackageAsync(...).GetWaiter().GetResult())
from net48 application also causes AddPackageAsync task never to resolve.
The deadlocks are reproduced ONLY with Release builds
net48 has a different WinRT runtime, not CsWinRT.
net48 has a different WinRT runtime, not CsWinRT.
We are able to reproduce the deadlock with both .net8.0 and net48. I know that net core uses CsWinRT package and net48 uses Microsoft.Windows.SDK.Contracts but I am not sure where to submit a bug for Microsoft.Windows.SDK.Contracts
We found that if we turn off optimization for (net48) in Release it works.
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<Optimize>False</Optimize>
</PropertyGroup>
@ekalchev Are you using CsWinRT projections that you generate in net48 or are you using the built-in support?
We are using this nuget version https://www.nuget.org/packages/Microsoft.Windows.SDK.Contracts. We tried older version and it is still reproducible.
As much as I know CsWinRT nuget package can be used only with .net core 5 or later. So for net48 we use the nuget package above. Keep in mind the issue is present in both .net48 apps and .net core apps
We also reproduce with with .net8 app and using <TargetFramework>net8-windows10.0.17763</TargetFramework>
If you can't reproduce it, we can provide a code that reproduce it.
Would be great to have some progress here - same issue with my project!
We have identified additional details regarding this issue. By passing a CancellationToken to the affected methods, the freezing problem is resolved. Here is an example of how to implement this:
using(CancellationTokenSource cts = new CancellationTokenSource ())
{
await packageManager.StagePackageAsync(uri, null).AsTask(cts .Token)
}
Using this approach, we conducted thousands of package installations with AddPackageAsync and StagePackageAsync, and none of these operations froze, unlike when they were executed without a CancellationToken. We hope this information helps in addressing the issue or provides a viable workaround.
We have identified additional details regarding this issue. By passing a CancellationToken to the affected methods, the freezing problem is resolved. Here is an example of how to implement this:
using(CancellationTokenSource cts = new CancellationTokenSource ()) { await packageManager.StagePackageAsync(uri, null).AsTask(cts .Token) }
Using this approach, we conducted thousands of package installations with AddPackageAsync and StagePackageAsync, and none of these operations froze, unlike when they were executed without a CancellationToken. We hope this information helps in addressing the issue or provides a viable workaround.
Does the workaround still work if CancellationToken.None is passed around?
Yes
I have also stumbled upon the same issue and it is very annoying. I would love to see this being worked on.
We have identified additional details regarding this issue. By passing a CancellationToken to the affected methods, the freezing problem is resolved. Here is an example of how to implement this: using(CancellationTokenSource cts = new CancellationTokenSource ()) { await packageManager.StagePackageAsync(uri, null).AsTask(cts .Token) } Using this approach, we conducted thousands of package installations with AddPackageAsync and StagePackageAsync, and none of these operations froze, unlike when they were executed without a CancellationToken. We hope this information helps in addressing the issue or provides a viable workaround.
Does the workaround still work if
CancellationToken.Noneis passed around?
Actually, we found another PackageManager method AddPackageByAppInstallerFileAsync. We tried with to call AsTask with new CancelationToken and CancellationToken.None but it the problem was still present.
The workaround with the cancellationToken works only if it is constructed by CancellationTokenSource like that.
using(CancellationTokenSource cts = new CancellationTokenSource ())
{
await packageManager.AddPackageByAppInstallerFileAsync(uri, null).AsTask(cts .Token)
}
I'm encountering the same problem. Could you please resolve it?
It is really frustrating that such an issue hasn't been addressed yet, especially since it only occurs in Release and not consistently. This behavior is extremely inconvenient and disruptive to the workflow. Why is this problem still unresolved? It's clearly an important issue, and it’s time it was properly addressed.
I'm encountering the same issue as well, so I kindly ask for it to be resolved.
This is still an issue, and for me even in a WinUI 3 app.
It’s concerning that this issue has persisted for years without an official response from Microsoft. The API is currently not functioning as expected, and we would appreciate timely communication and a resolution from the Microsoft team.
It seems the issue is with WindowsRuntimeSystemExtensions.AsTask() or specifically with how it wraps asynchronous Windows Runtime operations & awaits them.
That makes me ask has anyone tried to wrap asynchronous Windows Runtime operations themselves using TaskCompletionSource<T>/TaskCompletionSource & see if the issue still occurs? Because if it does then something is clearly wrong with WindowsRuntimeSystemExtensions.AsTask().
Here is an implementation for wrapping IAsyncOperationWithProgress<TResult, TProgress> as Task<TResult>which supports:
-
Attaching a handler for progress via callbacks.
-
Ability to cancel the asynchronous operation by passing a cancellation token.
-
Handler error propagation to underlying wrapping task.
public static async Task<TResult> Task<TResult, TProgress>(
this IAsyncOperationWithProgress<TResult, TProgress> info,
AsyncOperationProgressHandler<TResult, TProgress> handler = default,
CancellationToken token = default
)
{
// Check if the caller has passed a handler.
bool flag = handler is not null;
// Only asynchronously await if the operation hasn't completed.
if (info.Status is AsyncStatus.Started)
{
TaskCompletionSource<object> source = new();
// Propagate the errors from the caller's handler by wrapping it.
void Handler(IAsyncOperationWithProgress<TResult, TProgress> info, TProgress status)
{
try { handler(info, status); }
catch (Exception exception) { source.TrySetException(exception); }
}
// Attach the handler only if the caller passed one.
if (flag) info.Progress += Handler;
info.Completed += (info, status) =>
{
// Detach the handler only if the caller passed one.
if (flag) info.Progress -= Handler;
source.TrySetResult(null);
};
using var registration = token.Register(info.Cancel);
await source.Task.ConfigureAwait(false);
}
// Check if the operation was canceled, errored or completed.
return info.Status switch
{
AsyncStatus.Error => throw info.ErrorCode,
AsyncStatus.Canceled => throw new TaskCanceledException(),
_ => info.GetResults(),
};
}
Consuming the extension method that wraps the operation:
var manager = new PackageManager();
using CancellationTokenSource source = new();
await manager.AddPackageAsync(new("https://www.example.com"), null, DeploymentOptions.None).Task((info, progress) =>
{
Console.WriteLine($"{progress.state}, {progress.percentage}%");
}, source.Token);
Exception Stack Trace:
Unhandled Exception: System.Exception: Package could not be opened.
error 0x8007000D: Opening the package from location failed.
at Extensions.<Task>d__0`2.MoveNext() in ...
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Program.<Main>d__0.MoveNext() in ...
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Program.<Main>()
@Aetopia you are trying to install package from a invalid URL. "https://www.example.com"
@Aetopia you are trying to install package from a invalid URL. "https://www.example.com"
I just wanted to demo the extension method wrapper in action in this case by catching the error thrown by the operation due to a "non-existent" file.
The issue occurs only in Release builds and if there is no instance of CancellationToken passed and default is used ( or CancenllationToken.None), that suggest that the problem is somehow related to the generated IL by the compiler on probably we can't see it in C# code implementation.
The issue occurs only in Release builds and if there is no instance of CancellationToken passed and default is used ( or CancenllationToken.None), that suggest that the problem is somehow related to the generated IL by the compiler on probably we can't see it in C# code implementation.
The runtime + compiler implicitly calls WindowsRuntimeSystemExtensions.AsTask() to wrap an async operation. hence you may try inspecting System.Runtime.WindowsRuntime.dll via ILSpy or your preferred decompiler.
I get the gist of the issue though, my question is simply that has anyone tried to make their own async WinRT operation wrapper using TaskCompletionSource instead on relying on the one provided by the runtime, just to rule out its not a runtime specific issue.