CastException пытается вызвать Action <KeyValuePair <>> делегировать асинхронно - PullRequest
1 голос
/ 27 февраля 2009

Я не могу понять, почему я получаю 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: ответ Саймона помог подтвердить, что это поведение является неожиданным, однако реализация пользовательского типа не сработает для того, что я пытаюсь сделать. Я пытаюсь реализовать метод расширения в 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;
    }

1 Ответ

1 голос
/ 27 февраля 2009

Странно, похоже, что-то вроде ошибки в .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();
    }
}

Работает нормально. Переданный параметр translationType не передает .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);
...