Dasync/AsyncEnumerable

On the performance of Yield.SetCanceled function

ClockGet opened this issue · 5 comments

My English is not great, so I try to briefly describe my question.
I saw your source code, When I use the IAsyncEnumerableExtensions.Take function to returns a specified number of contiguous elements, I noticed that in Yield class ReturnAsync return a TaskCompletionSource object called _resumeEnumerationTcs, when AsyncEnumeratorWithState disposed, it will set yield Canceled, which triggered _resumeEnumerationTcs to set exception. I find out exception would reduce performance, Is it's deliberately designed like this? And can be replaced by TrySetCanceled? Thanks~

OK, it's not clear what's going on, so here is some info first:

  1. The _resumeEnumerationTcs is set to Canceled state by design. Please see section "4: Clean-up on incomplete enumeration" in the readme file for more info.
  2. TrySetCanceled won't change anything. The SetCanceled method internally calls TrySetCanceled, checks for the result, and throws an exception on false.

And help me to understand the problem:

  1. What kind of performance degradation do you observe?
  2. How do you measure that performance?
  3. Do you have using statements inside your async enumeration lambda function?

Thank you for your reply, I saw the readme file, it solves some of my doubts, and:

  1. I focus on how the excuting time of the same operation is as short as possible, so I think the execution time with using TrySetException may become longer.
  2. My programming is not as good as my English, so I wrote some simple code like:
static async Task TestNoTry()
{
	await Task.Run(() =>
	{
		TaskCompletionSource<int> taskCompletionSource = new TaskCompletionSource<int>();
		taskCompletionSource.TrySetResult(1);
		return taskCompletionSource.Task;
	}).ContinueWith(task => { });
}
static async Task TestHaveException()
{
	await Task.Run(() =>
	{
		TaskCompletionSource<int> taskCompletionSource = new TaskCompletionSource<int>();
		taskCompletionSource.TrySetException(new Exception());
		return taskCompletionSource.Task;
	}).ContinueWith(task => { });
}
static async Task TestHaveCanceled()
{
	await Task.Run(() =>
	{
		TaskCompletionSource<int> taskCompletionSource = new TaskCompletionSource<int>();
		taskCompletionSource.TrySetCanceled();
		return taskCompletionSource.Task;
	}).ContinueWith(task => { });
}
static async Task Run()
{
	Stopwatch sw = new Stopwatch();
	sw.Start();
	for (int loop = 0; loop < 100000; loop++)
	{
		await TestNoTry();
	}
	sw.Stop();
	Console.WriteLine(sw.ElapsedMilliseconds);
	sw.Restart();
	for (int loop = 0; loop < 100000; loop++)
	{
		await TestHaveException();
	}
	sw.Stop();
	Console.WriteLine(sw.ElapsedMilliseconds);
	sw.Restart();
	for (int loop = 0; loop < 100000; loop++)
	{
		await TestHaveCanceled();
	}
	sw.Stop();
	Console.WriteLine(sw.ElapsedMilliseconds);
}

Through these codes, I though it might be more efficient to use TrySetCanceled than TrySetException.
3. No, I don't have.

Well, thanks for the code, but I think you miss the crucial understanding about how async-await and TPL work, so the code tests nothing but speed of task execution using TPL.
And how is it related to this library?

I just use the code to test speed of task execution, and test that TrySetException would cost more time than TrySetCanceled, and proved that in this library when ReturnAsync return a task with a exception would cost more time, maybe I misunderstood async-await, TPL and your library, thanks again for your reply!

Yes, it's slower indeed. But if difference in ~2500 nanoseconds per iteration really matters for your application, you should not use neither IAsyncEnumerable nor IEnumerable :) That difference is negligible compared to heavy IO awaiting, which is the main target for this library.