如果资源是在async-await方法中,我遇到了一个问题,即在垃圾回收期间可能无法清除本地资源。
我已经创建了一些示例代码来说明问题。
SimpleClass
SimpleClass使用静态计数器通过在构造期间递增静态 _count 字段并在销毁期间递减相同字段来记录活动实例的数量。
using System;
namespace AsyncGarbageCollector
{
public class SimpleClass
{
private static readonly object CountLock = new object();
private static int _count;
public SimpleClass()
{
Console.WriteLine("Constructor is called");
lock (CountLock)
{
_count++;
}
}
~SimpleClass()
{
Console.WriteLine("Destructor is called");
lock (CountLock)
{
_count--;
}
}
public static int Count
{
get
{
lock (CountLock)
{
return _count;
}
}
}
}
}
程序
这是主程序,有三个测试
在每种情况下,在调用GC.Collect之前,变量将超出范围。因此,我希望在垃圾收集期间调用析构函数。
using System;
using System.Threading.Tasks;
namespace AsyncGarbageCollector
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Press 1, 2 or 3 to start.\n\n");
var code = Console.ReadKey(true);
if (code.Key == ConsoleKey.D1)
RunTest1();
else if (code.Key == ConsoleKey.D2)
RunTest2Async().Wait();
else if (code.Key == ConsoleKey.D3)
RunTest3Async().Wait();
Console.WriteLine("\n\nPress any key to close.");
Console.ReadKey();
}
private static void RunTest1()
{
Console.WriteLine("Test 1\n======");
TestCreation();
DisplayCounts();
}
private static async Task RunTest2Async()
{
Console.WriteLine("Test 2\n======");
await TestCreationAsync();
DisplayCounts();
}
private static async Task RunTest3Async()
{
Console.WriteLine("Test 3\n======");
await TestCreationNullAsync();
DisplayCounts();
}
private static void TestCreation()
{
var simple = new SimpleClass();
}
private static async Task TestCreationAsync()
{
var simple = new SimpleClass();
await Task.Delay(50);
}
private static async Task TestCreationNullAsync()
{
var simple = new SimpleClass();
await Task.Delay(50);
Console.WriteLine("Setting Null");
simple = null;
}
private static void DisplayCounts()
{
Console.WriteLine("Running GC.Collect()");
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Console.WriteLine("Count: " + SimpleClass.Count);
}
}
}
结果
Test 1
======
Constructor is called
Running GC.Collect()
Destructor is called
Count: 0
Returned to Main
Running GC.Collect()
Count: 0
Test 2
======
Constructor is called
Running GC.Collect()
Count: 1
Returned to Main
Running GC.Collect()
Destructor is called
Count: 0
Test 3
======
Constructor is called
Setting Null
Running GC.Collect()
Destructor is called
Count: 0
Returned to Main
Running GC.Collect()
Count: 0
在测试2中,SimpleClass对象中的析构函数不会被垃圾收集调用(即使它超出范围),直到从main函数调用垃圾收集。
这有充分的理由吗?我的猜测是异步方法本身仍然是“活着的”。直到所有相关的asyncs都已完成,因此其变量保持活着状态。
问题 - 在异步调用的生命周期内是否会收集本地对象?
任何答案/评论都将不胜感激。
答案 0 :(得分:7)
async/await
有点棘手。让我们仔细看看你的方法:
private static async Task RunTest2Async()
{
Console.WriteLine("Test 2\n======");
await TestCreationAsync();
DisplayCounts();
}
该方法在控制台上打印一些东西。然后它调用TestCreationAsync()
并返回Task
句柄。该方法将自身注册为任务的后继者,并返回任务句柄本身。编译器将方法转换为状态机以跟踪入口点。
然后,当TestCreationAsync()
返回的任务完成后,它再次调用RunTest2Async()
(使用指定的入口点)。当您处于调试模式时,您可以在调用堆栈中看到此信息。因此该方法仍然存在,因此创建的simple
仍在范围内。这就是为什么不收集它的原因。
如果您处于发布模式,simple
已经在await
续集中收集了{{1}}。可能是因为编译器发现它不再被使用了。所以在实践中,这应该不是问题。
这是一个可视化:
答案 1 :(得分:3)
Async只是一种使用状态机的便捷方式。
简单来说,当你写
时void async MyMethod()
{
int k = await Some1();
await Some2();
}
实际上你有一个像这样的结构(简化)
struct MyMethodState
{
int k;
int stage;
Task currentTaskToWaitFor;
}
并且编译器重写该方法以在各阶段之间移动(阶段由您使用的地点async
定义)
一个特别好的方法是使用Ildasm
查看内部。
所以是的,你保留了对象的引用。