Sho*_*ome 4 .net c# garbage-collection async-await
我遇到了一个问题,如果资源是在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;
}
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
程序
这是主程序,有三个测试
在每种情况下,在调用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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
结果
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
Run Code Online (Sandbox Code Playgroud)
在测试2中,SimpleClass对象中的析构函数不会被垃圾收集调用(即使它超出范围),直到从main函数调用垃圾收集.
有这么好的理由吗?我的猜测是,异步方法本身仍然是"活着的",直到所有相关的asyncs都已完成,因此其变量保持活着状态.
问题 - 在异步调用的生命周期内是否会收集本地对象?
任何答案/评论将不胜感激.
async/await有点棘手.让我们仔细看看你的方法:
private static async Task RunTest2Async()
{
Console.WriteLine("Test 2\n======");
await TestCreationAsync();
DisplayCounts();
}
Run Code Online (Sandbox Code Playgroud)
该方法在控制台上打印一些东西.然后调用TestCreationAsync()并Task返回句柄.该方法将自身注册为任务的后继者,并返回任务句柄本身.编译器将方法转换为状态机以跟踪入口点.
然后当返回的任务TestCreationAsync()完成时,它RunTest2Async()再次调用(使用指定的入口点).当您处于调试模式时,您可以在调用堆栈中看到这一点.因此该方法仍然存在,因此创建simple的仍然在范围内.这就是为什么它没有收集.
如果您处于发布模式,simple则已在await续集中收集.可能是因为编译器发现它不再被使用了.所以在实践中,这应该不是问题.
这是一个可视化:

| 归档时间: |
|
| 查看次数: |
1448 次 |
| 最近记录: |