CastException 尝试调用 Action> 异步委托

发布于 2024-07-14 05:08:15 字数 2037 浏览 8 评论 0 原文

我似乎无法弄清楚为什么我在运行以下代码时遇到 InvalidCastException:

var item = new KeyValuePair<string, string>("key", "value");

Action<KeyValuePair<string, string>> kvrAction = 
    kvr =>Console.WriteLine(kvr.Value);

var result = kvrAction.BeginInvoke(item, null, null);
kvrAction.EndInvoke(result);

异常信息:

Test method Utilities.Tests.IEnumerableExtensionTests.ProveDelegateAsyncInvokeFailsForKeyValuePair threw exception:  System.Runtime.Remoting.RemotingException: The argument type '[key, value]' cannot be converted into parameter type 'System.Collections.Generic.KeyValuePair`2[System.String,System.String]'.
--->  System.InvalidCastException: Object must implement IConvertible..

任何帮助将不胜感激=)此代码似乎适用于我抛出的任何东西,除了 KeyValuePair<>。

更新:似乎任何结构都存在这种情况。 我没有注意到 KeyValuePair<> 是一个结构体,因此仅使用类进行测试。 我仍然不明白为什么会这样。

更新 2:Simon 的回答有助于确认这种行为是意外的,但是实现自定义类型不适用于我想要做的事情。 我正在尝试在 IEnumerable<> 上实现扩展方法 为每个项目异步执行委托。 我注意到针对通用 Dictionary 对象运行测试时出错。

    public static IEnumerable<T> ForEachAsync<T>(this IEnumerable<T> input, Action<T> act)
    {
        foreach (var item in input)
        {
            act.BeginInvoke(item, new AsyncCallback(EndAsyncCall<T>), null);
        }

        return input;
    }

    private static void EndAsyncCall<T>(IAsyncResult result)
    {
        AsyncResult r = (AsyncResult)result;
        if (!r.EndInvokeCalled)
        {
            var d = (Action<T>)((r).AsyncDelegate);
            d.EndInvoke(result);
        }
    }

我宁愿不使用 T 上的约束来限制该方法,以确保仅使用类,因此我按如下方式重构了该方法,以解决 BeginInvoke 的问题,但我之前没有直接使用过 TreadPool,并且想确保我我没有错过任何重要的事情。

    public static IEnumerable<T> ForEachAsync<T>(this IEnumerable<T> input, Action<T> act)
    {
        foreach (var item in input)
            ThreadPool.QueueUserWorkItem(obj => act((T)obj), item);

        return input;
    }

I can't seem to figure out why I am getting an InvalidCastException running the following code:

var item = new KeyValuePair<string, string>("key", "value");

Action<KeyValuePair<string, string>> kvrAction = 
    kvr =>Console.WriteLine(kvr.Value);

var result = kvrAction.BeginInvoke(item, null, null);
kvrAction.EndInvoke(result);

Exception Info:

Test method Utilities.Tests.IEnumerableExtensionTests.ProveDelegateAsyncInvokeFailsForKeyValuePair threw exception:  System.Runtime.Remoting.RemotingException: The argument type '[key, value]' cannot be converted into parameter type 'System.Collections.Generic.KeyValuePair`2[System.String,System.String]'.
--->  System.InvalidCastException: Object must implement IConvertible..

Any assistance would be appreciated =) This code seems to work with anything I throw at it except a KeyValuePair<>.

Update: It appears this condition exists for any struct. I hadn't noticed KeyValuePair<> was a struct and so was only testing with classes. I still don't understand why this is the case though.

Update 2: Simon's answer helped confirm this behavior is unexpected, however implementing a custom type won't work for what I'm trying to do. I am trying to implement an extension method on IEnumerable<> to execute a delegate Asynchronously for each item. I noticed the error running tests against a generic Dictionary object.

    public static IEnumerable<T> ForEachAsync<T>(this IEnumerable<T> input, Action<T> act)
    {
        foreach (var item in input)
        {
            act.BeginInvoke(item, new AsyncCallback(EndAsyncCall<T>), null);
        }

        return input;
    }

    private static void EndAsyncCall<T>(IAsyncResult result)
    {
        AsyncResult r = (AsyncResult)result;
        if (!r.EndInvokeCalled)
        {
            var d = (Action<T>)((r).AsyncDelegate);
            d.EndInvoke(result);
        }
    }

I would rather not limit the method with a constraint on T to ensure only classes are used so I have refactored the method as follows to get around the problem with BeginInvoke but I have not worked with the TreadPool directly before and would like to make sure I am not missing anything important.

    public static IEnumerable<T> ForEachAsync<T>(this IEnumerable<T> input, Action<T> act)
    {
        foreach (var item in input)
            ThreadPool.QueueUserWorkItem(obj => act((T)obj), item);

        return input;
    }

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

爱的那么颓废 2024-07-21 05:08:15

奇怪的是,似乎是.NET(C#?)中将参数编组到工作线程时出现的某种错误。

如果您在传递的结构上实现 IConvertable:

struct MyPair<TKey, TValue> : IConvertable
{
    public readonly TKey Key;
    public readonly TValue Value;

    public MyPair(TKey key, TValue value)
    {
        Key = key;
        Value = value;
    }

    // I just used the smart-tag on IConvertable to get all these...
    // public X ToX(IFormatProvider provider) { throw new InvalidCastException(); }

    ...

    public object ToType(Type conversionType, IFormatProvider provider)
    {
        if (typeof(MyPair<TKey, TValue>).GUID == conversionType.GUID)
            return this;
        throw new InvalidCastException();
    }
}

它运行良好。 传递的 conversionType 不会传递 .Equal()、IsAssignableFrom() 或我尝试过的任何其他内容(除了 GUID 比较),这可能与它首先要求 IConvertable 的原因有关。

编辑:一个简单的解决方法是使用闭包来传递参数:

var data = new Dictionary<string, string> {
    { "Hello", "World" },
    { "How are", "You?" },
    { "Goodbye", "World!" }
};
foreach (var pair in data)
{
    var copy = pair; // define a different variable for each worker
    Action worker = () => Console.WriteLine("Item {0}, {1}", copy.Key, copy.Value);
    worker.BeginInvoke(null, null);
}

当然,如果您需要结果,则需要存储 IAsyncResults,这可能与另一个方向的参数有相同的问题。 作为替代方案,您可以在完成后将它们添加到集合中,但锁定会有点奇怪:

var data = new Dictionary<string, string> {
    { "Hello", "World" },
    { "How are", "You?" },
    { "Goodbye", "World!" }
};

var results = new List<KeyValuePair<string, string>>();
var pending = 0;
var done = new ManualResetEvent(false);

var workers = new List<Action>();
foreach (var pair in data)
{
    ++pending;
    var copy = pair; // define a different variable for each worker
    workers.Add(delegate()
    {
        Console.WriteLine("Item {0}, {1}", copy.Key, copy.Value);
        lock (results)
            results.Add(new KeyValuePair<string, string>("New " + copy.Key, "New " + copy.Value));
        if (0 == Interlocked.Decrement(ref pending))
            done.Set();
    });
}

foreach (var worker in workers)
    worker.BeginInvoke(null, null);

done.WaitOne();

foreach (var pair in results)
    Console.WriteLine("Result {0}, {1}", pair.Key, pair.Value);

Odd, seems to be some sort of bug in .NET (C#?) with marshalling the argument to the worker thread.

If you implement IConvertable on the passed struct:

struct MyPair<TKey, TValue> : IConvertable
{
    public readonly TKey Key;
    public readonly TValue Value;

    public MyPair(TKey key, TValue value)
    {
        Key = key;
        Value = value;
    }

    // I just used the smart-tag on IConvertable to get all these...
    // public X ToX(IFormatProvider provider) { throw new InvalidCastException(); }

    ...

    public object ToType(Type conversionType, IFormatProvider provider)
    {
        if (typeof(MyPair<TKey, TValue>).GUID == conversionType.GUID)
            return this;
        throw new InvalidCastException();
    }
}

It runs fine. The passed conversionType doesnt pass .Equal(), IsAssignableFrom(), or anything else I tried except GUID comparison, which is probably related to why it asks for an IConvertable in the first place.

EDIT: A simple workaround is to use closures to pass the parameter:

var data = new Dictionary<string, string> {
    { "Hello", "World" },
    { "How are", "You?" },
    { "Goodbye", "World!" }
};
foreach (var pair in data)
{
    var copy = pair; // define a different variable for each worker
    Action worker = () => Console.WriteLine("Item {0}, {1}", copy.Key, copy.Value);
    worker.BeginInvoke(null, null);
}

Of course, if you need the results, you will need to store the IAsyncResults, which will probably have the same issue as parameters, in the other direction. As an alternative, you could add them to a collection when they are complete, but the locking gets a bit weird:

var data = new Dictionary<string, string> {
    { "Hello", "World" },
    { "How are", "You?" },
    { "Goodbye", "World!" }
};

var results = new List<KeyValuePair<string, string>>();
var pending = 0;
var done = new ManualResetEvent(false);

var workers = new List<Action>();
foreach (var pair in data)
{
    ++pending;
    var copy = pair; // define a different variable for each worker
    workers.Add(delegate()
    {
        Console.WriteLine("Item {0}, {1}", copy.Key, copy.Value);
        lock (results)
            results.Add(new KeyValuePair<string, string>("New " + copy.Key, "New " + copy.Value));
        if (0 == Interlocked.Decrement(ref pending))
            done.Set();
    });
}

foreach (var worker in workers)
    worker.BeginInvoke(null, null);

done.WaitOne();

foreach (var pair in results)
    Console.WriteLine("Result {0}, {1}", pair.Key, pair.Value);
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文