让我们看一下显示问题的以下代码片段。
class Program
{
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");
});
task.Wait();
Console.Read();
}
private static async Task Start()
{
Console.WriteLine("Start");
Synchronizer sync = new Synchronizer();
var task = sync.SynchronizeAsync();
await task;
GC.KeepAlive(sync);//Keep alive or any method call doesn't help
sync.Dispose();//I need it here, But GC eats it :(
}
}
public class Synchronizer : IDisposable
{
private TaskCompletionSource<object> tcs;
public Synchronizer()
{
tcs = new TaskCompletionSource<object>(this);
}
~Synchronizer()
{
Console.WriteLine("~Synchronizer");
}
public void Dispose()
{
Console.WriteLine("Dispose");
}
public Task SynchronizeAsync()
{
return tcs.Task;
}
}
输出产生:
Start
Starting GC
~Synchronizer
GC Done
如您所见,sync
获取了Gc'd(更具体地说,已完成,我们不知道是否回收了内存)。但为什么?引用我的对象后,GC为什么会收集它?
研究:我花了一些时间调查幕后发生的事情,似乎由C#编译器生成的状态机被保留为局部变量,并且在第一次await
命中之后,状态机本身似乎超出了范围。
因此,这GC.KeepAlive(sync);
并sync.Dispose();
没有帮助,因为它们生活在状态机中,而状态机本身不在范围之内。
C#编译器不应该生成代码,以使我的sync
实例在仍然需要时会超出范围。这是C#编译器中的错误吗?还是我缺少基本的东西?
PS:我不是在寻找解决方法,而是在解释为什么编译器会这样做?我用谷歌搜索,但没有找到任何相关问题,如果对此重复感到抱歉。
Update1:我已经修改了TaskCompletionSource
创建内容以保存该Synchronizer
实例,但这仍然无济于事。
什么GC.KeepAlive(sync)
-本身是空白的-此处只是向编译器发出的将指令添加sync
到为struct
生成的状态机中的指令Start
。正如@usr所指出的,由其调用方返回的外部任务不包含对该内部状态机的引用。Start
另一方面,内部使用的TaskCompletionSource
'tcs.Task
任务Start
确实包含这样的引用(因为它持有对await
继续回调的引用,因此对整个状态机都有引用;该回调tcs.Task
在await
里面注册了Start
,在tcs.Task
和之间创建了循环引用。状态机)。但是,它们都tcs
没有tcs.Task
暴露在外面 Start
(可能已经被强引用了),因此状态机的对象图被隔离并进行了GC处理。
您可以通过创建明确的强引用来避免过早的GC tcs
:
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
在我可以通过进行对象访问之前,过早地对其进行GC处理是非常意外和不希望的task.Result
。
现在,更改Task.Delay(Timeout.Infinite)
为Task.Delay(Int32.MaxValue)
,一切按预期进行。
在内部,它归结为在await
继续回调对象(委托本身)上的强引用,该引用应在导致该回调的操作仍处于挂起状态(在运行中)时保留。我在“异步/等待,自定义等待者和垃圾收集器”中对此进行了解释。
IMO,此操作可能永无休止(如Task.Delay(Timeout.Infinite)
或不完整TaskCompletionSource
)的事实不应影响此行为。对于大多数自然异步操作,此类强引用确实由进行低级OS调用的基础.NET代码持有(例如在with中Task.Delay(Int32.MaxValue)
,它将回调传递给非托管Win32计时器API并通过保持GCHandle.Alloc
)。
如果没有待处理的任何级别的非托管调用(这可能是与情况Task.Delay(Timeout.Infinite)
,TaskCompletionSource
是一种冷Task
,定制awaiter),有一个地方没有明确的强引用,状态机的对象图纯粹是管理和隔离,所以确实发生了意外的GC。
我认为这是async/await
基础架构中的一个小设计折衷,以避免在standard内创建通常多余的强引用。ICriticalNotifyCompletion::UnsafeOnCompleted
TaskAwaiter
无论如何,使用自定义的等待者(可能称为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;
}
}
}
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);
}
}
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句