GC.KeepAlive(sync)(即blank by itself)在这里所做的只是向编译器发出的一条指令,将sync 对象添加到为Start 生成的状态机struct。正如@usr 所指出的,Start 返回给它的调用者的outer 任务确实不 包含对这个inner 状态机的引用。
另一方面,TaskCompletionSource 的tcs.Task 任务在Start 内部使用,确实包含这样的引用(因为它包含对await 延续回调的引用,因此是整个状态机;回调在await 内Start 上注册tcs.Task,在tcs.Task 和状态机之间创建循环引用)。然而,tcs 和 tcs.Task 都没有暴露在外部 Start(它可能是强引用的),因此状态机的对象图是隔离的并被 GC'ed。 p>
您可以通过创建对 tcs 的显式强引用来避免过早的 GC:
public Task SynchronizeAsync()
{
var gch = GCHandle.Alloc(tcs);
return tcs.Task.ContinueWith(
t => { gch.Free(); return t; },
TaskContinuationOptions.ExecuteSynchronously).Unwrap();
}
或者,使用async的更易读的版本:
public async Task SynchronizeAsync()
{
var gch = GCHandle.Alloc(tcs);
try
{
await tcs.Task;
}
finally
{
gch.Free();
}
}
为了更深入地研究这项研究,请考虑以下小改动,注意Task.Delay(Timeout.Infinite) 以及我返回并使用sync 作为Result 的Task<object> 的事实。没有好转:
private static async Task<object> Start()
{
Console.WriteLine("Start");
Synchronizer sync = new Synchronizer();
await Task.Delay(Timeout.Infinite);
// OR: await new Task<object>(() => sync);
// OR: await sync.SynchronizeAsync();
return sync;
}
static void Main(string[] args)
{
var task = Start();
Task.Run(() =>
{
Thread.Sleep(500);
Console.WriteLine("Starting GC");
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine("GC Done");
});
Console.WriteLine(task.Result);
Console.Read();
}
IMO,sync 对象在我可以通过 task.Result 访问它之前被过早地 GC 处理,这是非常出乎意料和不受欢迎的。。
现在,将Task.Delay(Timeout.Infinite) 更改为Task.Delay(Int32.MaxValue),一切正常。
在内部,它归结为对await 延续回调对象(委托本身)的强引用,当导致该回调的操作仍处于挂起状态(正在运行)时,它应该被持有。我在“Async/await, custom awaiter and garbage collector”中解释了这一点。
IMO,此操作可能永无止境(如Task.Delay(Timeout.Infinite) 或不完整的TaskCompletionSource)这一事实不应影响此行为。对于大多数自然异步操作,这种强引用确实由进行低级操作系统调用的底层 .NET 代码持有(例如 Task.Delay(Int32.MaxValue),它将回调传递给非托管 Win32 计时器 API 并保留它GCHandle.Alloc)。
如果在任何级别都没有挂起的非托管调用(Task.Delay(Timeout.Infinite)、TaskCompletionSource、冷Task、自定义等待程序可能是这种情况),则没有明确的强引用,状态机的对象图是完全托管和隔离的,所以意外的 GC 确实会发生。
我认为这是async/await 基础架构中的一个小设计折衷,以避免在标准TaskAwaiter 的ICriticalNotifyCompletion::UnsafeOnCompleted 中通常产生冗余强引用。
无论如何,一个可能通用的解决方案很容易实现,使用自定义等待器(我们称之为StrongAwaiter):
private static async Task<object> Start()
{
Console.WriteLine("Start");
Synchronizer sync = new Synchronizer();
await Task.Delay(Timeout.Infinite).WithStrongAwaiter();
// OR: await sync.SynchronizeAsync().WithStrongAwaiter();
return sync;
}
StrongAwaiter 本身(通用和非通用):
public static class TaskExt
{
// Generic Task<TResult>
public static StrongAwaiter<TResult> WithStrongAwaiter<TResult>(this Task<TResult> @task)
{
return new StrongAwaiter<TResult>(@task);
}
public class StrongAwaiter<TResult> :
System.Runtime.CompilerServices.ICriticalNotifyCompletion
{
Task<TResult> _task;
System.Runtime.CompilerServices.TaskAwaiter<TResult> _awaiter;
System.Runtime.InteropServices.GCHandle _gcHandle;
public StrongAwaiter(Task<TResult> task)
{
_task = task;
_awaiter = _task.GetAwaiter();
}
// custom Awaiter methods
public StrongAwaiter<TResult> GetAwaiter()
{
return this;
}
public bool IsCompleted
{
get { return _task.IsCompleted; }
}
public TResult GetResult()
{
return _awaiter.GetResult();
}
// INotifyCompletion
public void OnCompleted(Action continuation)
{
_awaiter.OnCompleted(WrapContinuation(continuation));
}
// ICriticalNotifyCompletion
public void UnsafeOnCompleted(Action continuation)
{
_awaiter.UnsafeOnCompleted(WrapContinuation(continuation));
}
Action WrapContinuation(Action continuation)
{
Action wrapper = () =>
{
_gcHandle.Free();
continuation();
};
_gcHandle = System.Runtime.InteropServices.GCHandle.Alloc(wrapper);
return wrapper;
}
}
// Non-generic Task
public static StrongAwaiter WithStrongAwaiter(this Task @task)
{
return new StrongAwaiter(@task);
}
public class StrongAwaiter :
System.Runtime.CompilerServices.ICriticalNotifyCompletion
{
Task _task;
System.Runtime.CompilerServices.TaskAwaiter _awaiter;
System.Runtime.InteropServices.GCHandle _gcHandle;
public StrongAwaiter(Task task)
{
_task = task;
_awaiter = _task.GetAwaiter();
}
// custom Awaiter methods
public StrongAwaiter GetAwaiter()
{
return this;
}
public bool IsCompleted
{
get { return _task.IsCompleted; }
}
public void GetResult()
{
_awaiter.GetResult();
}
// INotifyCompletion
public void OnCompleted(Action continuation)
{
_awaiter.OnCompleted(WrapContinuation(continuation));
}
// ICriticalNotifyCompletion
public void UnsafeOnCompleted(Action continuation)
{
_awaiter.UnsafeOnCompleted(WrapContinuation(continuation));
}
Action WrapContinuation(Action continuation)
{
Action wrapper = () =>
{
_gcHandle.Free();
continuation();
};
_gcHandle = System.Runtime.InteropServices.GCHandle.Alloc(wrapper);
return wrapper;
}
}
}
更新,这是一个真实的 Win32 互操作示例,说明保持
async 状态机活动的重要性。如果注释掉
GCHandle.Alloc(tcs) 和
gch.Free() 行,则发布版本将崩溃。必须固定
callback 或
tcs 才能正常工作。或者,可以使用
await tcs.Task.WithStrongAwaiter() 代替,利用上述
StrongAwaiter。
using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
public class Program
{
static async Task TestAsync()
{
var tcs = new TaskCompletionSource<bool>();
WaitOrTimerCallbackProc callback = (a, b) =>
tcs.TrySetResult(true);
//var gch = GCHandle.Alloc(tcs);
try
{
IntPtr timerHandle;
if (!CreateTimerQueueTimer(out timerHandle,
IntPtr.Zero,
callback,
IntPtr.Zero, 2000, 0, 0))
throw new System.ComponentModel.Win32Exception(
Marshal.GetLastWin32Error());
await tcs.Task;
}
finally
{
//gch.Free();
GC.KeepAlive(callback);
}
}
public static void Main(string[] args)
{
var task = TestAsync();
Task.Run(() =>
{
Thread.Sleep(500);
Console.WriteLine("Starting GC");
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine("GC Done");
});
task.Wait();
Console.WriteLine("completed!");
Console.Read();
}
// p/invoke
delegate void WaitOrTimerCallbackProc(IntPtr lpParameter, bool TimerOrWaitFired);
[DllImport("kernel32.dll")]
static extern bool CreateTimerQueueTimer(out IntPtr phNewTimer,
IntPtr TimerQueue, WaitOrTimerCallbackProc Callback, IntPtr Parameter,
uint DueTime, uint Period, uint Flags);
}
}