Cập nhật, một điểm tốt bởi @SriramSakthivel, nó quay ra tôi đã trả lời một câu hỏi rất giống nhau:
Why does GC collects my object when I have a reference to it?
Vì vậy, tôi đánh dấu này như là một cộng đồng wiki.
Tuy nhiên, giả sử SetResult (..) không bao giờ được gọi và someClassInstance dừng tham chiếu và bị thu thập rác. Điều này có tạo ra rò rỉ bộ nhớ không? Hoặc không .Net tự động biết một cách rõ ràng ngữ cảnh gọi là cần phải được xử lý?
Nếu bởi gọi-bối cảnh bạn có nghĩa là đối tượng máy nhà nước trình biên dịch tạo (đại diện cho nhà nước của phương pháp async
), sau đó có, nó sẽ thực sự được hoàn thành.
Ví dụ:
static void Main(string[] args)
{
var task = TestSomethingAsync();
Console.WriteLine("Press enter to GC");
Console.ReadLine();
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true);
GC.WaitForFullGCComplete();
GC.WaitForPendingFinalizers();
Console.WriteLine("Press enter to exit");
Console.ReadLine();
}
static async Task TestSomethingAsync()
{
using (var something = new SomeDisposable())
{
await something.WaitForThingAsync();
}
}
class SomeDisposable : IDisposable
{
readonly TaskCompletionSource<string> _tcs = new TaskCompletionSource<string>();
~SomeDisposable()
{
Console.WriteLine("~SomeDisposable");
}
public Task<string> WaitForThingAsync()
{
return _tcs.Task;
}
public void Dispose()
{
Console.WriteLine("SomeDisposable.Dispose");
GC.SuppressFinalize(this);
}
}
Output:
Press enter to GC
~SomeDisposable
Press enter to exit
IMO, hành vi này là hợp lý, nhưng nó còn có thể là một chút bất ngờ mà something
được hoàn thành mặc dù thực tế rằng using
phạm vi cho nó chưa bao giờ kết thúc (và do đó SomeDisposable.Dispose
của nó chưa bao giờ được gọi) và rằng Task
được trả lại bởi TestSomethingAsync
vẫn còn hoạt động và được tham chiếu trong Main
.
Điều này có thể dẫn đến một số lỗi không rõ ràng khi mã hóa nội dung không đồng bộ ở cấp hệ thống. Điều thực sự quan trọng là sử dụng GCHandle.Alloc(callback)
trên bất kỳ cuộc gọi lại interop hệ điều hành nào không được tham chiếu bên ngoài các phương thức async
. Làm GC.KeepAlive(callback)
một mình ở cuối phương pháp async
không hiệu quả. Tôi đã viết về điều này trong chi tiết tại đây:
Async/await, custom awaiter and garbage collector
Trên một mặt lưu ý, có một loại C máy # trạng thái: một phương thức với return yield
. Điều thú vị là, cùng với IEnumerable
hoặc IEnumerator
, nó cũng thực hiện IDisposable
. Gọi Dispose
của nó sẽ bung ra bất kỳ using
và finally
báo cáo (ngay cả trong trường hợp chuỗi đếm không đầy đủ):
static IEnumerator SomethingEnumerable()
{
using (var disposable = new SomeDisposable())
{
try
{
Console.WriteLine("Step 1");
yield return null;
Console.WriteLine("Step 2");
yield return null;
Console.WriteLine("Step 3");
yield return null;
}
finally
{
Console.WriteLine("Finally");
}
}
}
// ...
var something = SomethingEnumerable();
something.MoveNext(); // prints "Step 1"
var disposable = (IDisposable)something;
disposable.Dispose(); // prints "Finally", "SomeDisposable.Dispose"
Không giống như này, với async
phương pháp không có cách nào trực tiếp của việc kiểm soát unwiding của using
và finally
.
TCS là một đối tượng bị cô lập. Khi nó không thể truy cập, nó sẽ được thu thập. Nhiệm vụ của nó không tham chiếu đến TCS để bạn có thể có một nhiệm vụ không thể hoàn thành mà không có bất kỳ TCS tương ứng nào trong bộ nhớ. Nếu nhiệm vụ đó không được chấp nhận thì nó cũng được thu thập. – usr