了解委托和任务
Understanding Delegates and Task
public static Task<TResult> ForEachParallel<TItem, TSubResult, TResult, TParam>(this IEnumerable items, Func<TItem, TParam, TSubResult> map, Func<TSubResult[], TResult> reduce, TParam param)
{
if (items == null) { throw new ArgumentNullException("items"); }
if (map == null) { throw new ArgumentNullException("map"); }
if (reduce == null) { throw new ArgumentNullException("reduce"); }
return Task<TResult>.Factory.StartNew(() =>
{
List<Task<TSubResult>> tasks = new List<Task<TSubResult>>();
foreach (TItem item in items)
{
Task<TSubResult> t = Task<TSubResult>.Factory.StartNew(item2 =>
{
var mparam = (Tuple<TItem, TParam>)item2;
return map(mparam.Item1, mparam.Item2);
},
new Tuple<TItem, TParam>(item, param),
TaskCreationOptions.None | TaskCreationOptions.AttachedToParent);
tasks.Add(t);
}
List<TSubResult> results = new List<TSubResult>();
foreach (Task<TSubResult> task in tasks)
{
results.Add(task.Result);
}
return reduce(results.ToArray());
});
}
static void Main(string[] args)
{
var a = Generate().ForEachParallel<int, int, int, int>(
(element, param) =>
{
var result = element * param;
Console.WriteLine("Map: {0}, {1}", result, Task.CurrentId);
Thread.Sleep(new Random(DateTime.Now.Millisecond).Next(500));
return result;
},
(subresult) =>
{
var sum = 0;
foreach (var item in subresult)
{
sum += item;
}
Console.WriteLine("Reduce: {0}", sum);
return sum;
},
5);
Console.WriteLine(a.Result);
}
}
static IEnumerable Generate()
{
for (int i = 0; i < 100; i++)
{
yield return i;
}
}
我有几个关于该计划的问题:
- 在
main()
中,为什么作者没有填充4个值
(即,元素,参数,子结果)?
- 在
main()
中,什么是元素和参数值?
- 在
ForEachParallel()
中,new Tuple<TItem, TParam>(item, param)
的作用是什么?
- 在
ForEachParallel()
、var mparam = (Tuple<TItem, TParam>)item2
中,item2是什么?作者是否调用元组
功能?
用一个简单的例子来解释这一点是最容易的。
此代码:
private static string UppercaseString(string inputString)
{
return inputString.ToUpper();
}
public static void Main()
{
var result = UppercaseString("hello");
}
与以下内容完全相同:
private static string UppercaseString(string inputString)
{
return inputString.ToUpper();
}
public static void Main()
{
Func<string, string> convert = UppercaseString;
var result = convert("hello");
}
和完全一样:
public static void Main()
{
Func<string, string> convert = inputString => inputString.ToUpper();
var result = convert("hello");
}
第一个例子是传统的做事方式。
第二个示例使用 Func<string, string>
创建指向方法的指针(委托)。
第三个例子使用lambda表达式。
在所有情况下,inputString
是方法的参数。 inputString
的值在调用方法时设置为"hello"
。
示例代码中的 element
、param
、subresult
和 item2
也是如此。它们都是代表的参数。无论调用这些委托的是什么,都负责输入参数。
可能使您的代码更难理解的是委托在其他方法中用作参数。例如:
private static string Hello(Func<string, string> func)
{
return func("hello");
}
public static void Main()
{
Func<string, string> convert = inputString => inputString.ToUpper();
var result = Hello(convert);
}
另请参阅有关此主题的 MSDN 文档:https://msdn.microsoft.com/en-us/library/bb549151(v=vs.110).aspx
在深入研究这段代码的工作原理之前,了解一些 C# 语言理论非常有用。我会在每个答案旁边标明 link。
a).b). 您期望填充的元素将在调用 lambda 表达式时具有值。您在 main 中看到的是 lambda 表达式,它们在声明时不被调用,但在使用时被调用。
第一个 lambda 将在行 return map(mparam.Item1, mparam.Item2);
上调用
第二个 lambda 将在行 return reduce(results.ToArray());
上调用
此 lambda 表达式的起点可以是 here.
c). 调用 StartNew 方法时,您可以选择使用不同的方法集 (method overloading) 调用它。所以这里调用了这个特定的方法签名:
public Task StartNew(Action<Object> action,
Object state,
TaskCreationOptions creationOptions)
这意味着这个方法的第二个参数(state)将作为前一个动作的输入参数。
d). 所以新创建的Tuple
对象会被传递给lambda表达式:
item2 =>
{
var mparam = (Tuple<TItem, TParam>)item2;
return map(mparam.Item1, mparam.Item2);
},
基本上,Action<object>
委托代表一个方法,它接受一个对象类型的参数作为输入,但您需要一个元组来访问它的项(Item1 和 Item2)。此行 var mparam = (Tuple<TItem, TParam>)item2
进行了从对象到元组的显式转换。
要找出为什么没有传递第 4 个参数,您需要了解 Method Extensions。实际上第一个参数没有传递,因为该方法是通过使用 IEnumerable 类型的对象调用的。此方法扩展了 class IEnumerable 而未触及原始 class 所以说 'I want this method to extend IEnumerable and use it for every IEnumerable object' 的方法是将第一个参数设置为 IEnumerable。编译器将确保将列表作为第一个参数发送,以便在扩展方法中使用它。
public static Task<TResult> ForEachParallel<TItem, TSubResult, TResult, TParam>(this IEnumerable items, Func<TItem, TParam, TSubResult> map, Func<TSubResult[], TResult> reduce, TParam param)
{
if (items == null) { throw new ArgumentNullException("items"); }
if (map == null) { throw new ArgumentNullException("map"); }
if (reduce == null) { throw new ArgumentNullException("reduce"); }
return Task<TResult>.Factory.StartNew(() =>
{
List<Task<TSubResult>> tasks = new List<Task<TSubResult>>();
foreach (TItem item in items)
{
Task<TSubResult> t = Task<TSubResult>.Factory.StartNew(item2 =>
{
var mparam = (Tuple<TItem, TParam>)item2;
return map(mparam.Item1, mparam.Item2);
},
new Tuple<TItem, TParam>(item, param),
TaskCreationOptions.None | TaskCreationOptions.AttachedToParent);
tasks.Add(t);
}
List<TSubResult> results = new List<TSubResult>();
foreach (Task<TSubResult> task in tasks)
{
results.Add(task.Result);
}
return reduce(results.ToArray());
});
}
static void Main(string[] args)
{
var a = Generate().ForEachParallel<int, int, int, int>(
(element, param) =>
{
var result = element * param;
Console.WriteLine("Map: {0}, {1}", result, Task.CurrentId);
Thread.Sleep(new Random(DateTime.Now.Millisecond).Next(500));
return result;
},
(subresult) =>
{
var sum = 0;
foreach (var item in subresult)
{
sum += item;
}
Console.WriteLine("Reduce: {0}", sum);
return sum;
},
5);
Console.WriteLine(a.Result);
}
}
static IEnumerable Generate()
{
for (int i = 0; i < 100; i++)
{
yield return i;
}
}
我有几个关于该计划的问题:
- 在
main()
中,为什么作者没有填充4个值 (即,元素,参数,子结果)? - 在
main()
中,什么是元素和参数值? - 在
ForEachParallel()
中,new Tuple<TItem, TParam>(item, param)
的作用是什么? - 在
ForEachParallel()
、var mparam = (Tuple<TItem, TParam>)item2
中,item2是什么?作者是否调用元组 功能?
用一个简单的例子来解释这一点是最容易的。
此代码:
private static string UppercaseString(string inputString)
{
return inputString.ToUpper();
}
public static void Main()
{
var result = UppercaseString("hello");
}
与以下内容完全相同:
private static string UppercaseString(string inputString)
{
return inputString.ToUpper();
}
public static void Main()
{
Func<string, string> convert = UppercaseString;
var result = convert("hello");
}
和完全一样:
public static void Main()
{
Func<string, string> convert = inputString => inputString.ToUpper();
var result = convert("hello");
}
第一个例子是传统的做事方式。
第二个示例使用 Func<string, string>
创建指向方法的指针(委托)。
第三个例子使用lambda表达式。
在所有情况下,inputString
是方法的参数。 inputString
的值在调用方法时设置为"hello"
。
示例代码中的 element
、param
、subresult
和 item2
也是如此。它们都是代表的参数。无论调用这些委托的是什么,都负责输入参数。
可能使您的代码更难理解的是委托在其他方法中用作参数。例如:
private static string Hello(Func<string, string> func)
{
return func("hello");
}
public static void Main()
{
Func<string, string> convert = inputString => inputString.ToUpper();
var result = Hello(convert);
}
另请参阅有关此主题的 MSDN 文档:https://msdn.microsoft.com/en-us/library/bb549151(v=vs.110).aspx
在深入研究这段代码的工作原理之前,了解一些 C# 语言理论非常有用。我会在每个答案旁边标明 link。
a).b). 您期望填充的元素将在调用 lambda 表达式时具有值。您在 main 中看到的是 lambda 表达式,它们在声明时不被调用,但在使用时被调用。
第一个 lambda 将在行 return map(mparam.Item1, mparam.Item2);
上调用
第二个 lambda 将在行 return reduce(results.ToArray());
上调用
此 lambda 表达式的起点可以是 here.
c). 调用 StartNew 方法时,您可以选择使用不同的方法集 (method overloading) 调用它。所以这里调用了这个特定的方法签名:
public Task StartNew(Action<Object> action,
Object state,
TaskCreationOptions creationOptions)
这意味着这个方法的第二个参数(state)将作为前一个动作的输入参数。
d). 所以新创建的Tuple
对象会被传递给lambda表达式:
item2 =>
{
var mparam = (Tuple<TItem, TParam>)item2;
return map(mparam.Item1, mparam.Item2);
},
基本上,Action<object>
委托代表一个方法,它接受一个对象类型的参数作为输入,但您需要一个元组来访问它的项(Item1 和 Item2)。此行 var mparam = (Tuple<TItem, TParam>)item2
进行了从对象到元组的显式转换。
要找出为什么没有传递第 4 个参数,您需要了解 Method Extensions。实际上第一个参数没有传递,因为该方法是通过使用 IEnumerable 类型的对象调用的。此方法扩展了 class IEnumerable 而未触及原始 class 所以说 'I want this method to extend IEnumerable and use it for every IEnumerable object' 的方法是将第一个参数设置为 IEnumerable。编译器将确保将列表作为第一个参数发送,以便在扩展方法中使用它。