Fix consume ValueTask backed by IValueTaskSource (#2108)
* Added AwaitHelper to properly wait for ValueTasks. * Adjust `AwaitHelper` to allow multiple threads to use it concurrently. * Changed AwaitHelper to static. * Add test case to make sure ValueTasks work properly with a race condition between `IsCompleted` and `OnCompleted`. Changed AwaitHelper to use `ManualResetEventSlim` instead of `Monitor.Wait`. * Make `ValueTaskWaiter.Wait` generic. * Compare types directly.
This commit is contained in:
Родитель
0d3099163a
Коммит
7306ee7def
|
@ -63,7 +63,7 @@ namespace BenchmarkDotNet.Code
|
|||
(method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>) ||
|
||||
method.ReturnType.GetGenericTypeDefinition() == typeof(ValueTask<>))))
|
||||
{
|
||||
return $"() => {method.Name}().GetAwaiter().GetResult()";
|
||||
return $"() => BenchmarkDotNet.Helpers.AwaitHelper.GetResult({method.Name}())";
|
||||
}
|
||||
|
||||
return method.Name;
|
||||
|
@ -149,12 +149,10 @@ namespace BenchmarkDotNet.Code
|
|||
{
|
||||
public TaskDeclarationsProvider(Descriptor descriptor) : base(descriptor) { }
|
||||
|
||||
// we use GetAwaiter().GetResult() because it's fastest way to obtain the result in blocking way,
|
||||
// and will eventually throw actual exception, not aggregated one
|
||||
public override string WorkloadMethodDelegate(string passArguments)
|
||||
=> $"({passArguments}) => {{ {Descriptor.WorkloadMethod.Name}({passArguments}).GetAwaiter().GetResult(); }}";
|
||||
=> $"({passArguments}) => {{ BenchmarkDotNet.Helpers.AwaitHelper.GetResult({Descriptor.WorkloadMethod.Name}({passArguments})); }}";
|
||||
|
||||
public override string GetWorkloadMethodCall(string passArguments) => $"{Descriptor.WorkloadMethod.Name}({passArguments}).GetAwaiter().GetResult()";
|
||||
public override string GetWorkloadMethodCall(string passArguments) => $"BenchmarkDotNet.Helpers.AwaitHelper.GetResult({Descriptor.WorkloadMethod.Name}({passArguments}))";
|
||||
|
||||
protected override Type WorkloadMethodReturnType => typeof(void);
|
||||
}
|
||||
|
@ -168,11 +166,9 @@ namespace BenchmarkDotNet.Code
|
|||
|
||||
protected override Type WorkloadMethodReturnType => Descriptor.WorkloadMethod.ReturnType.GetTypeInfo().GetGenericArguments().Single();
|
||||
|
||||
// we use GetAwaiter().GetResult() because it's fastest way to obtain the result in blocking way,
|
||||
// and will eventually throw actual exception, not aggregated one
|
||||
public override string WorkloadMethodDelegate(string passArguments)
|
||||
=> $"({passArguments}) => {{ return {Descriptor.WorkloadMethod.Name}({passArguments}).GetAwaiter().GetResult(); }}";
|
||||
=> $"({passArguments}) => {{ return BenchmarkDotNet.Helpers.AwaitHelper.GetResult({Descriptor.WorkloadMethod.Name}({passArguments})); }}";
|
||||
|
||||
public override string GetWorkloadMethodCall(string passArguments) => $"{Descriptor.WorkloadMethod.Name}({passArguments}).GetAwaiter().GetResult()";
|
||||
public override string GetWorkloadMethodCall(string passArguments) => $"BenchmarkDotNet.Helpers.AwaitHelper.GetResult({Descriptor.WorkloadMethod.Name}({passArguments}))";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BenchmarkDotNet.Helpers
|
||||
{
|
||||
public static class AwaitHelper
|
||||
{
|
||||
private class ValueTaskWaiter
|
||||
{
|
||||
// We use thread static field so that each thread uses its own individual callback and reset event.
|
||||
[ThreadStatic]
|
||||
private static ValueTaskWaiter ts_current;
|
||||
internal static ValueTaskWaiter Current => ts_current ??= new ValueTaskWaiter();
|
||||
|
||||
// We cache the callback to prevent allocations for memory diagnoser.
|
||||
private readonly Action awaiterCallback;
|
||||
private readonly ManualResetEventSlim resetEvent;
|
||||
|
||||
private ValueTaskWaiter()
|
||||
{
|
||||
resetEvent = new ();
|
||||
awaiterCallback = resetEvent.Set;
|
||||
}
|
||||
|
||||
internal void Wait<TAwaiter>(TAwaiter awaiter) where TAwaiter : ICriticalNotifyCompletion
|
||||
{
|
||||
resetEvent.Reset();
|
||||
awaiter.UnsafeOnCompleted(awaiterCallback);
|
||||
|
||||
// The fastest way to wait for completion is to spin a bit before waiting on the event. This is the same logic that Task.GetAwaiter().GetResult() uses.
|
||||
var spinner = new SpinWait();
|
||||
while (!resetEvent.IsSet)
|
||||
{
|
||||
if (spinner.NextSpinWillYield)
|
||||
{
|
||||
resetEvent.Wait();
|
||||
return;
|
||||
}
|
||||
spinner.SpinOnce();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we use GetAwaiter().GetResult() because it's fastest way to obtain the result in blocking way,
|
||||
// and will eventually throw actual exception, not aggregated one
|
||||
public static void GetResult(Task task) => task.GetAwaiter().GetResult();
|
||||
|
||||
public static T GetResult<T>(Task<T> task) => task.GetAwaiter().GetResult();
|
||||
|
||||
// ValueTask can be backed by an IValueTaskSource that only supports asynchronous awaits,
|
||||
// so we have to hook up a callback instead of calling .GetAwaiter().GetResult() like we do for Task.
|
||||
// The alternative is to convert it to Task using .AsTask(), but that causes allocations which we must avoid for memory diagnoser.
|
||||
public static void GetResult(ValueTask task)
|
||||
{
|
||||
// Don't continue on the captured context, as that may result in a deadlock if the user runs this in-process.
|
||||
var awaiter = task.ConfigureAwait(false).GetAwaiter();
|
||||
if (!awaiter.IsCompleted)
|
||||
{
|
||||
ValueTaskWaiter.Current.Wait(awaiter);
|
||||
}
|
||||
awaiter.GetResult();
|
||||
}
|
||||
|
||||
public static T GetResult<T>(ValueTask<T> task)
|
||||
{
|
||||
// Don't continue on the captured context, as that may result in a deadlock if the user runs this in-process.
|
||||
var awaiter = task.ConfigureAwait(false).GetAwaiter();
|
||||
if (!awaiter.IsCompleted)
|
||||
{
|
||||
ValueTaskWaiter.Current.Wait(awaiter);
|
||||
}
|
||||
return awaiter.GetResult();
|
||||
}
|
||||
|
||||
internal static MethodInfo GetGetResultMethod(Type taskType)
|
||||
{
|
||||
if (!taskType.IsGenericType)
|
||||
{
|
||||
return typeof(AwaitHelper).GetMethod(nameof(AwaitHelper.GetResult), BindingFlags.Public | BindingFlags.Static, null, new Type[1] { taskType }, null);
|
||||
}
|
||||
|
||||
Type compareType = taskType.GetGenericTypeDefinition() == typeof(ValueTask<>) ? typeof(ValueTask<>)
|
||||
: typeof(Task).IsAssignableFrom(taskType.GetGenericTypeDefinition()) ? typeof(Task<>)
|
||||
: null;
|
||||
if (compareType == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var resultType = taskType
|
||||
.GetMethod(nameof(Task.GetAwaiter), BindingFlags.Public | BindingFlags.Instance)
|
||||
.ReturnType
|
||||
.GetMethod(nameof(TaskAwaiter.GetResult), BindingFlags.Public | BindingFlags.Instance)
|
||||
.ReturnType;
|
||||
return typeof(AwaitHelper).GetMethods(BindingFlags.Public | BindingFlags.Static)
|
||||
.First(m =>
|
||||
{
|
||||
if (m.Name != nameof(AwaitHelper.GetResult)) return false;
|
||||
Type paramType = m.GetParameters().First().ParameterType;
|
||||
return paramType.IsGenericType && paramType.GetGenericTypeDefinition() == compareType;
|
||||
})
|
||||
.MakeGenericMethod(new[] { resultType });
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
using BenchmarkDotNet.Engines;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -16,28 +18,24 @@ namespace BenchmarkDotNet.Toolchains.InProcess.Emit.Implementation
|
|||
|
||||
OriginMethodReturnType = methodReturnType;
|
||||
|
||||
// Please note this code does not support await over extension methods.
|
||||
var getAwaiterMethod = methodReturnType.GetMethod(nameof(Task<int>.GetAwaiter), BindingFlagsPublicInstance);
|
||||
if (getAwaiterMethod == null)
|
||||
// Only support (Value)Task for parity with other toolchains (and so we can use AwaitHelper).
|
||||
IsAwaitable = methodReturnType == typeof(Task) || methodReturnType == typeof(ValueTask)
|
||||
|| (methodReturnType.GetTypeInfo().IsGenericType
|
||||
&& (methodReturnType.GetTypeInfo().GetGenericTypeDefinition() == typeof(Task<>)
|
||||
|| methodReturnType.GetTypeInfo().GetGenericTypeDefinition() == typeof(ValueTask<>)));
|
||||
|
||||
if (!IsAwaitable)
|
||||
{
|
||||
WorkloadMethodReturnType = methodReturnType;
|
||||
}
|
||||
else
|
||||
{
|
||||
var getResultMethod = getAwaiterMethod
|
||||
WorkloadMethodReturnType = methodReturnType
|
||||
.GetMethod(nameof(Task.GetAwaiter), BindingFlagsPublicInstance)
|
||||
.ReturnType
|
||||
.GetMethod(nameof(TaskAwaiter.GetResult), BindingFlagsPublicInstance);
|
||||
|
||||
if (getResultMethod == null)
|
||||
{
|
||||
WorkloadMethodReturnType = methodReturnType;
|
||||
}
|
||||
else
|
||||
{
|
||||
WorkloadMethodReturnType = getResultMethod.ReturnType;
|
||||
GetAwaiterMethod = getAwaiterMethod;
|
||||
GetResultMethod = getResultMethod;
|
||||
}
|
||||
.GetMethod(nameof(TaskAwaiter.GetResult), BindingFlagsPublicInstance)
|
||||
.ReturnType;
|
||||
GetResultMethod = Helpers.AwaitHelper.GetGetResultMethod(methodReturnType);
|
||||
}
|
||||
|
||||
if (WorkloadMethodReturnType == null)
|
||||
|
@ -74,7 +72,6 @@ namespace BenchmarkDotNet.Toolchains.InProcess.Emit.Implementation
|
|||
public Type WorkloadMethodReturnType { get; }
|
||||
public Type OverheadMethodReturnType { get; }
|
||||
|
||||
public MethodInfo? GetAwaiterMethod { get; }
|
||||
public MethodInfo? GetResultMethod { get; }
|
||||
|
||||
public bool IsVoid { get; }
|
||||
|
@ -82,6 +79,6 @@ namespace BenchmarkDotNet.Toolchains.InProcess.Emit.Implementation
|
|||
public bool IsConsumable { get; }
|
||||
public FieldInfo? WorkloadConsumableField { get; }
|
||||
|
||||
public bool IsAwaitable => GetAwaiterMethod != null && GetResultMethod != null;
|
||||
public bool IsAwaitable { get; }
|
||||
}
|
||||
}
|
|
@ -434,7 +434,7 @@ namespace BenchmarkDotNet.Toolchains.InProcess.Emit.Implementation
|
|||
|
||||
Type argLocalsType;
|
||||
Type argFieldType;
|
||||
MethodInfo? opConversion = null;
|
||||
MethodInfo opConversion = null;
|
||||
if (parameterType.IsByRef)
|
||||
{
|
||||
argLocalsType = parameterType;
|
||||
|
@ -582,42 +582,28 @@ namespace BenchmarkDotNet.Toolchains.InProcess.Emit.Implementation
|
|||
workloadInvokeMethod.ReturnParameter,
|
||||
args);
|
||||
args = methodBuilder.GetEmitParameters(args);
|
||||
var callResultType = consumableInfo.OriginMethodReturnType;
|
||||
var awaiterType = consumableInfo.GetAwaiterMethod?.ReturnType
|
||||
?? throw new InvalidOperationException($"Bug: {nameof(consumableInfo.GetAwaiterMethod)} is null");
|
||||
|
||||
var ilBuilder = methodBuilder.GetILGenerator();
|
||||
|
||||
/*
|
||||
.locals init (
|
||||
[0] valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<int32>
|
||||
)
|
||||
*/
|
||||
var callResultLocal =
|
||||
ilBuilder.DeclareOptionalLocalForInstanceCall(callResultType, consumableInfo.GetAwaiterMethod);
|
||||
var awaiterLocal =
|
||||
ilBuilder.DeclareOptionalLocalForInstanceCall(awaiterType, consumableInfo.GetResultMethod);
|
||||
|
||||
/*
|
||||
// return TaskSample(arg0). ... ;
|
||||
IL_0000: ldarg.0
|
||||
IL_0001: ldarg.1
|
||||
IL_0002: call instance class [mscorlib]System.Threading.Tasks.Task`1<int32> [BenchmarkDotNet]BenchmarkDotNet.Samples.SampleBenchmark::TaskSample(int64)
|
||||
*/
|
||||
IL_0026: ldarg.0
|
||||
IL_0027: ldloc.0
|
||||
IL_0028: ldloc.1
|
||||
IL_0029: ldloc.2
|
||||
IL_002a: ldloc.3
|
||||
IL_002b: call instance class [System.Private.CoreLib]System.Threading.Tasks.Task`1<object> BenchmarkDotNet.Helpers.Runnable_0::WorkloadMethod(string, string, string, string)
|
||||
*/
|
||||
if (!Descriptor.WorkloadMethod.IsStatic)
|
||||
ilBuilder.Emit(OpCodes.Ldarg_0);
|
||||
ilBuilder.EmitLdargs(args);
|
||||
ilBuilder.Emit(OpCodes.Call, Descriptor.WorkloadMethod);
|
||||
|
||||
/*
|
||||
// ... .GetAwaiter().GetResult();
|
||||
IL_0007: callvirt instance valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<!0> class [mscorlib]System.Threading.Tasks.Task`1<int32>::GetAwaiter()
|
||||
IL_000c: stloc.0
|
||||
IL_000d: ldloca.s 0
|
||||
IL_000f: call instance !0 valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<int32>::GetResult()
|
||||
*/
|
||||
ilBuilder.EmitInstanceCallThisValueOnStack(callResultLocal, consumableInfo.GetAwaiterMethod);
|
||||
ilBuilder.EmitInstanceCallThisValueOnStack(awaiterLocal, consumableInfo.GetResultMethod);
|
||||
// BenchmarkDotNet.Helpers.AwaitHelper.GetResult(...);
|
||||
IL_000e: call !!0 BenchmarkDotNet.Helpers.AwaitHelper::GetResult<int32>(valuetype [System.Runtime]System.Threading.Tasks.ValueTask`1<!!0>)
|
||||
*/
|
||||
|
||||
ilBuilder.Emit(OpCodes.Call, consumableInfo.GetResultMethod);
|
||||
|
||||
/*
|
||||
IL_0014: ret
|
||||
|
@ -833,19 +819,6 @@ namespace BenchmarkDotNet.Toolchains.InProcess.Emit.Implementation
|
|||
var skipFirstArg = workloadMethod.IsStatic;
|
||||
var argLocals = EmitDeclareArgLocals(ilBuilder, skipFirstArg);
|
||||
|
||||
LocalBuilder? callResultLocal = null;
|
||||
LocalBuilder? awaiterLocal = null;
|
||||
if (consumableInfo.IsAwaitable)
|
||||
{
|
||||
var callResultType = consumableInfo.OriginMethodReturnType;
|
||||
var awaiterType = consumableInfo.GetAwaiterMethod?.ReturnType
|
||||
?? throw new InvalidOperationException($"Bug: {nameof(consumableInfo.GetAwaiterMethod)} is null");
|
||||
callResultLocal =
|
||||
ilBuilder.DeclareOptionalLocalForInstanceCall(callResultType, consumableInfo.GetAwaiterMethod);
|
||||
awaiterLocal =
|
||||
ilBuilder.DeclareOptionalLocalForInstanceCall(awaiterType, consumableInfo.GetResultMethod);
|
||||
}
|
||||
|
||||
consumeEmitter.DeclareDisassemblyDiagnoserLocals(ilBuilder);
|
||||
|
||||
var notElevenLabel = ilBuilder.DefineLabel();
|
||||
|
@ -870,29 +843,27 @@ namespace BenchmarkDotNet.Toolchains.InProcess.Emit.Implementation
|
|||
EmitLoadArgFieldsToLocals(ilBuilder, argLocals, skipFirstArg);
|
||||
|
||||
/*
|
||||
// return TaskSample(_argField) ... ;
|
||||
IL_0011: ldarg.0
|
||||
IL_0012: ldloc.0
|
||||
IL_0013: call instance class [mscorlib]System.Threading.Tasks.Task`1<int32> [BenchmarkDotNet]BenchmarkDotNet.Samples.SampleBenchmark::TaskSample(int64)
|
||||
IL_0018: ret
|
||||
IL_0026: ldarg.0
|
||||
IL_0027: ldloc.0
|
||||
IL_0028: ldloc.1
|
||||
IL_0029: ldloc.2
|
||||
IL_002a: ldloc.3
|
||||
IL_002b: call instance class [System.Private.CoreLib]System.Threading.Tasks.Task`1<object> BenchmarkDotNet.Helpers.Runnable_0::WorkloadMethod(string, string, string, string)
|
||||
*/
|
||||
|
||||
if (!workloadMethod.IsStatic)
|
||||
{
|
||||
ilBuilder.Emit(OpCodes.Ldarg_0);
|
||||
}
|
||||
ilBuilder.EmitLdLocals(argLocals);
|
||||
ilBuilder.Emit(OpCodes.Call, workloadMethod);
|
||||
|
||||
if (consumableInfo.IsAwaitable)
|
||||
{
|
||||
/*
|
||||
// ... .GetAwaiter().GetResult();
|
||||
IL_0007: callvirt instance valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<!0> class [mscorlib]System.Threading.Tasks.Task`1<int32>::GetAwaiter()
|
||||
IL_000c: stloc.0
|
||||
IL_000d: ldloca.s 0
|
||||
IL_000f: call instance !0 valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<int32>::GetResult()
|
||||
*/
|
||||
ilBuilder.EmitInstanceCallThisValueOnStack(callResultLocal, consumableInfo.GetAwaiterMethod);
|
||||
ilBuilder.EmitInstanceCallThisValueOnStack(awaiterLocal, consumableInfo.GetResultMethod);
|
||||
// BenchmarkDotNet.Helpers.AwaitHelper.GetResult(...);
|
||||
IL_000e: call !!0 BenchmarkDotNet.Helpers.AwaitHelper::GetResult<int32>(valuetype [System.Runtime]System.Threading.Tasks.ValueTask`1<!!0>)
|
||||
*/
|
||||
ilBuilder.Emit(OpCodes.Call, consumableInfo.GetResultMethod);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -118,7 +118,7 @@ namespace BenchmarkDotNet.Toolchains.InProcess.NoEmit
|
|||
private void Overhead() { }
|
||||
|
||||
// must be kept in sync with TaskDeclarationsProvider.TargetMethodDelegate
|
||||
private void ExecuteBlocking() => startTaskCallback.Invoke().GetAwaiter().GetResult();
|
||||
private void ExecuteBlocking() => Helpers.AwaitHelper.GetResult(startTaskCallback.Invoke());
|
||||
|
||||
[MethodImpl(CodeGenHelper.AggressiveOptimizationOption)]
|
||||
private void WorkloadActionUnroll(long repeatCount)
|
||||
|
@ -165,7 +165,7 @@ namespace BenchmarkDotNet.Toolchains.InProcess.NoEmit
|
|||
private T Overhead() => default;
|
||||
|
||||
// must be kept in sync with GenericTaskDeclarationsProvider.TargetMethodDelegate
|
||||
private T ExecuteBlocking() => startTaskCallback().GetAwaiter().GetResult();
|
||||
private T ExecuteBlocking() => Helpers.AwaitHelper.GetResult(startTaskCallback.Invoke());
|
||||
|
||||
private void InvokeSingleHardcoded() => result = callback();
|
||||
|
||||
|
@ -217,7 +217,7 @@ namespace BenchmarkDotNet.Toolchains.InProcess.NoEmit
|
|||
private T Overhead() => default;
|
||||
|
||||
// must be kept in sync with GenericTaskDeclarationsProvider.TargetMethodDelegate
|
||||
private T ExecuteBlocking() => startTaskCallback().GetAwaiter().GetResult();
|
||||
private T ExecuteBlocking() => Helpers.AwaitHelper.GetResult(startTaskCallback.Invoke());
|
||||
|
||||
private void InvokeSingleHardcoded() => result = callback();
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ using System.Reflection;
|
|||
using System.Threading.Tasks;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Extensions;
|
||||
using BenchmarkDotNet.Helpers;
|
||||
using BenchmarkDotNet.Running;
|
||||
|
||||
namespace BenchmarkDotNet.Validators
|
||||
|
@ -130,21 +131,8 @@ namespace BenchmarkDotNet.Validators
|
|||
return;
|
||||
}
|
||||
|
||||
var returnType = result.GetType();
|
||||
if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(ValueTask<>))
|
||||
{
|
||||
var asTaskMethod = result.GetType().GetMethod("AsTask");
|
||||
result = asTaskMethod.Invoke(result, null);
|
||||
}
|
||||
|
||||
if (result is Task task)
|
||||
{
|
||||
task.GetAwaiter().GetResult();
|
||||
}
|
||||
else if (result is ValueTask valueTask)
|
||||
{
|
||||
valueTask.GetAwaiter().GetResult();
|
||||
}
|
||||
AwaitHelper.GetGetResultMethod(result.GetType())
|
||||
?.Invoke(null, new[] { result });
|
||||
}
|
||||
|
||||
private bool TryToSetParamsFields(object benchmarkTypeInstance, List<ValidationError> errors)
|
||||
|
|
|
@ -48,13 +48,20 @@ namespace BenchmarkDotNet.IntegrationTests
|
|||
private static string[] GetActualLogLines(Summary summary)
|
||||
=> GetSingleStandardOutput(summary).Where(line => line.StartsWith(Prefix)).ToArray();
|
||||
|
||||
[Fact]
|
||||
public void AllSetupAndCleanupMethodRunsTest()
|
||||
[Theory]
|
||||
[InlineData(typeof(AllSetupAndCleanupAttributeBenchmarks))]
|
||||
[InlineData(typeof(AllSetupAndCleanupAttributeBenchmarksTask))]
|
||||
[InlineData(typeof(AllSetupAndCleanupAttributeBenchmarksGenericTask))]
|
||||
[InlineData(typeof(AllSetupAndCleanupAttributeBenchmarksValueTask))]
|
||||
[InlineData(typeof(AllSetupAndCleanupAttributeBenchmarksGenericValueTask))]
|
||||
[InlineData(typeof(AllSetupAndCleanupAttributeBenchmarksValueTaskSource))]
|
||||
[InlineData(typeof(AllSetupAndCleanupAttributeBenchmarksGenericValueTaskSource))]
|
||||
public void AllSetupAndCleanupMethodRunsTest(Type benchmarkType)
|
||||
{
|
||||
var miniJob = Job.Default.WithStrategy(RunStrategy.Monitoring).WithWarmupCount(2).WithIterationCount(3).WithInvocationCount(1).WithUnrollFactor(1).WithId("MiniJob");
|
||||
var config = CreateSimpleConfig(job: miniJob);
|
||||
|
||||
var summary = CanExecute<AllSetupAndCleanupAttributeBenchmarks>(config);
|
||||
var summary = CanExecute(benchmarkType, config);
|
||||
|
||||
var actualLogLines = GetActualLogLines(summary);
|
||||
foreach (string line in actualLogLines)
|
||||
|
@ -83,56 +90,7 @@ namespace BenchmarkDotNet.IntegrationTests
|
|||
public void Benchmark() => Console.WriteLine(BenchmarkCalled);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AllSetupAndCleanupMethodRunsAsyncTest()
|
||||
{
|
||||
var miniJob = Job.Default.WithStrategy(RunStrategy.Monitoring).WithWarmupCount(2).WithIterationCount(3).WithInvocationCount(1).WithUnrollFactor(1).WithId("MiniJob");
|
||||
var config = CreateSimpleConfig(job: miniJob);
|
||||
|
||||
var summary = CanExecute<AllSetupAndCleanupAttributeBenchmarksAsync>(config);
|
||||
|
||||
var actualLogLines = GetActualLogLines(summary);
|
||||
foreach (string line in actualLogLines)
|
||||
Output.WriteLine(line);
|
||||
Assert.Equal(expectedLogLines, actualLogLines);
|
||||
}
|
||||
|
||||
public class AllSetupAndCleanupAttributeBenchmarksAsync
|
||||
{
|
||||
private int setupCounter;
|
||||
private int cleanupCounter;
|
||||
|
||||
[IterationSetup]
|
||||
public void IterationSetup() => Console.WriteLine(IterationSetupCalled + " (" + ++setupCounter + ")");
|
||||
|
||||
[IterationCleanup]
|
||||
public void IterationCleanup() => Console.WriteLine(IterationCleanupCalled + " (" + ++cleanupCounter + ")");
|
||||
|
||||
[GlobalSetup]
|
||||
public Task GlobalSetup() => Console.Out.WriteLineAsync(GlobalSetupCalled);
|
||||
|
||||
[GlobalCleanup]
|
||||
public Task GlobalCleanup() => Console.Out.WriteLineAsync(GlobalCleanupCalled);
|
||||
|
||||
[Benchmark]
|
||||
public Task Benchmark() => Console.Out.WriteLineAsync(BenchmarkCalled);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AllSetupAndCleanupMethodRunsAsyncTaskSetupTest()
|
||||
{
|
||||
var miniJob = Job.Default.WithStrategy(RunStrategy.Monitoring).WithWarmupCount(2).WithIterationCount(3).WithInvocationCount(1).WithUnrollFactor(1).WithId("MiniJob");
|
||||
var config = CreateSimpleConfig(job: miniJob);
|
||||
|
||||
var summary = CanExecute<AllSetupAndCleanupAttributeBenchmarksAsyncTaskSetup>(config);
|
||||
|
||||
var actualLogLines = GetActualLogLines(summary);
|
||||
foreach (string line in actualLogLines)
|
||||
Output.WriteLine(line);
|
||||
Assert.Equal(expectedLogLines, actualLogLines);
|
||||
}
|
||||
|
||||
public class AllSetupAndCleanupAttributeBenchmarksAsyncTaskSetup
|
||||
public class AllSetupAndCleanupAttributeBenchmarksTask
|
||||
{
|
||||
private int setupCounter;
|
||||
private int cleanupCounter;
|
||||
|
@ -153,21 +111,7 @@ namespace BenchmarkDotNet.IntegrationTests
|
|||
public void Benchmark() => Console.WriteLine(BenchmarkCalled);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AllSetupAndCleanupMethodRunsAsyncGenericTaskSetupTest()
|
||||
{
|
||||
var miniJob = Job.Default.WithStrategy(RunStrategy.Monitoring).WithWarmupCount(2).WithIterationCount(3).WithInvocationCount(1).WithUnrollFactor(1).WithId("MiniJob");
|
||||
var config = CreateSimpleConfig(job: miniJob);
|
||||
|
||||
var summary = CanExecute<AllSetupAndCleanupAttributeBenchmarksAsyncGenericTaskSetup>(config);
|
||||
|
||||
var actualLogLines = GetActualLogLines(summary);
|
||||
foreach (string line in actualLogLines)
|
||||
Output.WriteLine(line);
|
||||
Assert.Equal(expectedLogLines, actualLogLines);
|
||||
}
|
||||
|
||||
public class AllSetupAndCleanupAttributeBenchmarksAsyncGenericTaskSetup
|
||||
public class AllSetupAndCleanupAttributeBenchmarksGenericTask
|
||||
{
|
||||
private int setupCounter;
|
||||
private int cleanupCounter;
|
||||
|
@ -198,21 +142,7 @@ namespace BenchmarkDotNet.IntegrationTests
|
|||
public void Benchmark() => Console.WriteLine(BenchmarkCalled);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AllSetupAndCleanupMethodRunsAsyncValueTaskSetupTest()
|
||||
{
|
||||
var miniJob = Job.Default.WithStrategy(RunStrategy.Monitoring).WithWarmupCount(2).WithIterationCount(3).WithInvocationCount(1).WithUnrollFactor(1).WithId("MiniJob");
|
||||
var config = CreateSimpleConfig(job: miniJob);
|
||||
|
||||
var summary = CanExecute<AllSetupAndCleanupAttributeBenchmarksAsyncValueTaskSetup>(config);
|
||||
|
||||
var actualLogLines = GetActualLogLines(summary);
|
||||
foreach (string line in actualLogLines)
|
||||
Output.WriteLine(line);
|
||||
Assert.Equal(expectedLogLines, actualLogLines);
|
||||
}
|
||||
|
||||
public class AllSetupAndCleanupAttributeBenchmarksAsyncValueTaskSetup
|
||||
public class AllSetupAndCleanupAttributeBenchmarksValueTask
|
||||
{
|
||||
private int setupCounter;
|
||||
private int cleanupCounter;
|
||||
|
@ -233,21 +163,7 @@ namespace BenchmarkDotNet.IntegrationTests
|
|||
public void Benchmark() => Console.WriteLine(BenchmarkCalled);
|
||||
}
|
||||
|
||||
[FactEnvSpecific(EnvRequirement.NonWindows)]
|
||||
public void AllSetupAndCleanupMethodRunsAsyncGenericValueTaskSetupTest()
|
||||
{
|
||||
var miniJob = Job.Default.WithStrategy(RunStrategy.Monitoring).WithWarmupCount(2).WithIterationCount(3).WithInvocationCount(1).WithUnrollFactor(1).WithId("MiniJob");
|
||||
var config = CreateSimpleConfig(job: miniJob);
|
||||
|
||||
var summary = CanExecute<AllSetupAndCleanupAttributeBenchmarksAsyncGenericValueTaskSetup>(config);
|
||||
|
||||
var actualLogLines = GetActualLogLines(summary);
|
||||
foreach (string line in actualLogLines)
|
||||
Output.WriteLine(line);
|
||||
Assert.Equal(expectedLogLines, actualLogLines);
|
||||
}
|
||||
|
||||
public class AllSetupAndCleanupAttributeBenchmarksAsyncGenericValueTaskSetup
|
||||
public class AllSetupAndCleanupAttributeBenchmarksGenericValueTask
|
||||
{
|
||||
private int setupCounter;
|
||||
private int cleanupCounter;
|
||||
|
@ -277,5 +193,69 @@ namespace BenchmarkDotNet.IntegrationTests
|
|||
[Benchmark]
|
||||
public void Benchmark() => Console.WriteLine(BenchmarkCalled);
|
||||
}
|
||||
|
||||
public class AllSetupAndCleanupAttributeBenchmarksValueTaskSource
|
||||
{
|
||||
private readonly ValueTaskSource<int> valueTaskSource = new ();
|
||||
private int setupCounter;
|
||||
private int cleanupCounter;
|
||||
|
||||
[IterationSetup]
|
||||
public void IterationSetup() => Console.WriteLine(IterationSetupCalled + " (" + ++setupCounter + ")");
|
||||
|
||||
[IterationCleanup]
|
||||
public void IterationCleanup() => Console.WriteLine(IterationCleanupCalled + " (" + ++cleanupCounter + ")");
|
||||
|
||||
[GlobalSetup]
|
||||
public ValueTask GlobalSetup()
|
||||
{
|
||||
valueTaskSource.Reset();
|
||||
Console.Out.WriteLineAsync(GlobalSetupCalled).ContinueWith(_ => valueTaskSource.SetResult(42));
|
||||
return new ValueTask(valueTaskSource, valueTaskSource.Token);
|
||||
}
|
||||
|
||||
[GlobalCleanup]
|
||||
public ValueTask GlobalCleanup()
|
||||
{
|
||||
valueTaskSource.Reset();
|
||||
Console.Out.WriteLineAsync(GlobalCleanupCalled).ContinueWith(_ => valueTaskSource.SetResult(42));
|
||||
return new ValueTask(valueTaskSource, valueTaskSource.Token);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Benchmark() => Console.WriteLine(BenchmarkCalled);
|
||||
}
|
||||
|
||||
public class AllSetupAndCleanupAttributeBenchmarksGenericValueTaskSource
|
||||
{
|
||||
private readonly ValueTaskSource<int> valueTaskSource = new ();
|
||||
private int setupCounter;
|
||||
private int cleanupCounter;
|
||||
|
||||
[IterationSetup]
|
||||
public void IterationSetup() => Console.WriteLine(IterationSetupCalled + " (" + ++setupCounter + ")");
|
||||
|
||||
[IterationCleanup]
|
||||
public void IterationCleanup() => Console.WriteLine(IterationCleanupCalled + " (" + ++cleanupCounter + ")");
|
||||
|
||||
[GlobalSetup]
|
||||
public ValueTask<int> GlobalSetup()
|
||||
{
|
||||
valueTaskSource.Reset();
|
||||
Console.Out.WriteLineAsync(GlobalSetupCalled).ContinueWith(_ => valueTaskSource.SetResult(42));
|
||||
return new ValueTask<int>(valueTaskSource, valueTaskSource.Token);
|
||||
}
|
||||
|
||||
[GlobalCleanup]
|
||||
public ValueTask<int> GlobalCleanup()
|
||||
{
|
||||
valueTaskSource.Reset();
|
||||
Console.Out.WriteLineAsync(GlobalCleanupCalled).ContinueWith(_ => valueTaskSource.SetResult(42));
|
||||
return new ValueTask<int>(valueTaskSource, valueTaskSource.Token);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Benchmark() => Console.WriteLine(BenchmarkCalled);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,10 +1,44 @@
|
|||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading.Tasks.Sources;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace BenchmarkDotNet.IntegrationTests
|
||||
{
|
||||
internal class ValueTaskSource<T> : IValueTaskSource<T>, IValueTaskSource
|
||||
{
|
||||
private ManualResetValueTaskSourceCore<T> _core;
|
||||
|
||||
T IValueTaskSource<T>.GetResult(short token) => _core.GetResult(token);
|
||||
void IValueTaskSource.GetResult(short token) => _core.GetResult(token);
|
||||
ValueTaskSourceStatus IValueTaskSource<T>.GetStatus(short token) => _core.GetStatus(token);
|
||||
ValueTaskSourceStatus IValueTaskSource.GetStatus(short token) => _core.GetStatus(token);
|
||||
void IValueTaskSource<T>.OnCompleted(Action<object> continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) => _core.OnCompleted(continuation, state, token, flags);
|
||||
void IValueTaskSource.OnCompleted(Action<object> continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) => _core.OnCompleted(continuation, state, token, flags);
|
||||
public void Reset() => _core.Reset();
|
||||
public short Token => _core.Version;
|
||||
public void SetResult(T result) => _core.SetResult(result);
|
||||
}
|
||||
|
||||
// This is used to test the case of ValueTaskAwaiter.IsCompleted returns false, then OnCompleted invokes the callback immediately because it happened to complete between the 2 calls.
|
||||
internal class ValueTaskSourceCallbackOnly<T> : IValueTaskSource<T>, IValueTaskSource
|
||||
{
|
||||
private ManualResetValueTaskSourceCore<T> _core;
|
||||
|
||||
T IValueTaskSource<T>.GetResult(short token) => _core.GetResult(token);
|
||||
void IValueTaskSource.GetResult(short token) => _core.GetResult(token);
|
||||
// Always return pending state so OnCompleted will be called.
|
||||
ValueTaskSourceStatus IValueTaskSource<T>.GetStatus(short token) => ValueTaskSourceStatus.Pending;
|
||||
ValueTaskSourceStatus IValueTaskSource.GetStatus(short token) => ValueTaskSourceStatus.Pending;
|
||||
void IValueTaskSource<T>.OnCompleted(Action<object> continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) => _core.OnCompleted(continuation, state, token, flags);
|
||||
void IValueTaskSource.OnCompleted(Action<object> continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) => _core.OnCompleted(continuation, state, token, flags);
|
||||
public void Reset() => _core.Reset();
|
||||
public short Token => _core.Version;
|
||||
public void SetResult(T result) => _core.SetResult(result);
|
||||
}
|
||||
|
||||
public class AsyncBenchmarksTests : BenchmarkTestExecutor
|
||||
{
|
||||
public AsyncBenchmarksTests(ITestOutputHelper output) : base(output) { }
|
||||
|
@ -24,8 +58,13 @@ namespace BenchmarkDotNet.IntegrationTests
|
|||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TaskReturningMethodsAreAwaited_AlreadyComplete() => CanExecute<TaskImmediateMethods>();
|
||||
|
||||
public class TaskDelayMethods
|
||||
{
|
||||
private readonly ValueTaskSource<int> valueTaskSource = new ();
|
||||
|
||||
private const int MillisecondsDelay = 100;
|
||||
|
||||
internal const double NanosecondsDelay = MillisecondsDelay * 1e+6;
|
||||
|
@ -39,6 +78,17 @@ namespace BenchmarkDotNet.IntegrationTests
|
|||
[Benchmark]
|
||||
public ValueTask ReturningValueTask() => new ValueTask(Task.Delay(MillisecondsDelay));
|
||||
|
||||
[Benchmark]
|
||||
public ValueTask ReturningValueTaskBackByIValueTaskSource()
|
||||
{
|
||||
valueTaskSource.Reset();
|
||||
Task.Delay(MillisecondsDelay).ContinueWith(_ =>
|
||||
{
|
||||
valueTaskSource.SetResult(default);
|
||||
});
|
||||
return new ValueTask(valueTaskSource, valueTaskSource.Token);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public async Task Awaiting() => await Task.Delay(MillisecondsDelay);
|
||||
|
||||
|
@ -47,6 +97,70 @@ namespace BenchmarkDotNet.IntegrationTests
|
|||
|
||||
[Benchmark]
|
||||
public ValueTask<int> ReturningGenericValueTask() => new ValueTask<int>(ReturningGenericTask());
|
||||
|
||||
[Benchmark]
|
||||
public ValueTask<int> ReturningGenericValueTaskBackByIValueTaskSource()
|
||||
{
|
||||
valueTaskSource.Reset();
|
||||
Task.Delay(MillisecondsDelay).ContinueWith(_ =>
|
||||
{
|
||||
valueTaskSource.SetResult(default);
|
||||
});
|
||||
return new ValueTask<int>(valueTaskSource, valueTaskSource.Token);
|
||||
}
|
||||
}
|
||||
|
||||
public class TaskImmediateMethods
|
||||
{
|
||||
private readonly ValueTaskSource<int> valueTaskSource = new ();
|
||||
private readonly ValueTaskSourceCallbackOnly<int> valueTaskSourceCallbackOnly = new ();
|
||||
|
||||
[Benchmark]
|
||||
public Task ReturningTask() => Task.CompletedTask;
|
||||
|
||||
[Benchmark]
|
||||
public ValueTask ReturningValueTask() => new ValueTask();
|
||||
|
||||
[Benchmark]
|
||||
public ValueTask ReturningValueTaskBackByIValueTaskSource()
|
||||
{
|
||||
valueTaskSource.Reset();
|
||||
valueTaskSource.SetResult(default);
|
||||
return new ValueTask(valueTaskSource, valueTaskSource.Token);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public ValueTask ReturningValueTaskBackByIValueTaskSource_ImmediateCallback()
|
||||
{
|
||||
valueTaskSourceCallbackOnly.Reset();
|
||||
valueTaskSourceCallbackOnly.SetResult(default);
|
||||
return new ValueTask(valueTaskSourceCallbackOnly, valueTaskSourceCallbackOnly.Token);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public async Task Awaiting() => await Task.CompletedTask;
|
||||
|
||||
[Benchmark]
|
||||
public Task<int> ReturningGenericTask() => ReturningTask().ContinueWith(_ => default(int));
|
||||
|
||||
[Benchmark]
|
||||
public ValueTask<int> ReturningGenericValueTask() => new ValueTask<int>(ReturningGenericTask());
|
||||
|
||||
[Benchmark]
|
||||
public ValueTask<int> ReturningGenericValueTaskBackByIValueTaskSource()
|
||||
{
|
||||
valueTaskSource.Reset();
|
||||
valueTaskSource.SetResult(default);
|
||||
return new ValueTask<int>(valueTaskSource, valueTaskSource.Token);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public ValueTask<int> ReturningGenericValueTaskBackByIValueTaskSource_ImmediateCallback()
|
||||
{
|
||||
valueTaskSourceCallbackOnly.Reset();
|
||||
valueTaskSourceCallbackOnly.SetResult(default);
|
||||
return new ValueTask<int>(valueTaskSourceCallbackOnly, valueTaskSourceCallbackOnly.Token);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -153,6 +153,13 @@ namespace BenchmarkDotNet.IntegrationTests
|
|||
Interlocked.Increment(ref Counter);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public async ValueTask InvokeOnceValueTaskAsync()
|
||||
{
|
||||
await Task.Yield();
|
||||
Interlocked.Increment(ref Counter);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public string InvokeOnceRefType()
|
||||
{
|
||||
|
@ -195,6 +202,13 @@ namespace BenchmarkDotNet.IntegrationTests
|
|||
Interlocked.Increment(ref Counter);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public static async ValueTask InvokeOnceStaticValueTaskAsync()
|
||||
{
|
||||
await Task.Yield();
|
||||
Interlocked.Increment(ref Counter);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public static string InvokeOnceStaticRefType()
|
||||
{
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading.Tasks.Sources;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Running;
|
||||
using BenchmarkDotNet.Validators;
|
||||
|
@ -563,5 +564,82 @@ namespace BenchmarkDotNet.Tests.Validators
|
|||
[Benchmark]
|
||||
public void NonThrowing() { }
|
||||
}
|
||||
|
||||
private class ValueTaskSource<T> : IValueTaskSource<T>, IValueTaskSource
|
||||
{
|
||||
private ManualResetValueTaskSourceCore<T> _core;
|
||||
|
||||
T IValueTaskSource<T>.GetResult(short token) => _core.GetResult(token);
|
||||
void IValueTaskSource.GetResult(short token) => _core.GetResult(token);
|
||||
ValueTaskSourceStatus IValueTaskSource<T>.GetStatus(short token) => _core.GetStatus(token);
|
||||
ValueTaskSourceStatus IValueTaskSource.GetStatus(short token) => _core.GetStatus(token);
|
||||
void IValueTaskSource<T>.OnCompleted(Action<object> continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) => _core.OnCompleted(continuation, state, token, flags);
|
||||
void IValueTaskSource.OnCompleted(Action<object> continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) => _core.OnCompleted(continuation, state, token, flags);
|
||||
public void Reset() => _core.Reset();
|
||||
public short Token => _core.Version;
|
||||
public void SetResult(T result) => _core.SetResult(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AsyncValueTaskBackedByIValueTaskSourceIsAwaitedProperly()
|
||||
{
|
||||
var validationErrors = ExecutionValidator.FailOnError.Validate(BenchmarkConverter.TypeToBenchmarks(typeof(AsyncValueTaskSource))).ToList();
|
||||
|
||||
Assert.True(AsyncValueTaskSource.WasCalled);
|
||||
Assert.Empty(validationErrors);
|
||||
}
|
||||
|
||||
public class AsyncValueTaskSource
|
||||
{
|
||||
private readonly ValueTaskSource<bool> valueTaskSource = new ();
|
||||
|
||||
public static bool WasCalled;
|
||||
|
||||
[GlobalSetup]
|
||||
public ValueTask GlobalSetup()
|
||||
{
|
||||
valueTaskSource.Reset();
|
||||
Task.Delay(1).ContinueWith(_ =>
|
||||
{
|
||||
WasCalled = true;
|
||||
valueTaskSource.SetResult(true);
|
||||
});
|
||||
return new ValueTask(valueTaskSource, valueTaskSource.Token);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void NonThrowing() { }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AsyncGenericValueTaskBackedByIValueTaskSourceIsAwaitedProperly()
|
||||
{
|
||||
var validationErrors = ExecutionValidator.FailOnError.Validate(BenchmarkConverter.TypeToBenchmarks(typeof(AsyncGenericValueTaskSource))).ToList();
|
||||
|
||||
Assert.True(AsyncGenericValueTaskSource.WasCalled);
|
||||
Assert.Empty(validationErrors);
|
||||
}
|
||||
|
||||
public class AsyncGenericValueTaskSource
|
||||
{
|
||||
private readonly ValueTaskSource<int> valueTaskSource = new ();
|
||||
|
||||
public static bool WasCalled;
|
||||
|
||||
[GlobalSetup]
|
||||
public ValueTask<int> GlobalSetup()
|
||||
{
|
||||
valueTaskSource.Reset();
|
||||
Task.Delay(1).ContinueWith(_ =>
|
||||
{
|
||||
WasCalled = true;
|
||||
valueTaskSource.SetResult(1);
|
||||
});
|
||||
return new ValueTask<int>(valueTaskSource, valueTaskSource.Token);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void NonThrowing() { }
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче