2009-02-27 6 views
1

je ne peux pas à comprendre pourquoi je reçois un InvalidCastException exécutant le code suivant:CastException essayer d'appeler Action <KeyValuePair <>> délégué de façon asynchrone

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.. 

Toute aide serait appréciée =) Ce code semble fonctionner avec tout ce que je lui lance sauf un KeyValuePair <>.

Mise à jour: Il semble que cette condition existe pour toute structure. Je n'avais pas remarqué KeyValuePair <> était une structure et ne faisait donc que tester les classes. Je ne comprends toujours pas pourquoi c'est le cas.

Mise à jour 2: La réponse de Simon a permis de confirmer que ce comportement est inattendu, mais l'implémentation d'un type personnalisé ne fonctionnera pas pour ce que j'essaie de faire. J'essaie d'implémenter une méthode d'extension sur IEnumerable <> pour exécuter un délégué de manière asynchrone pour chaque élément. J'ai remarqué l'erreur exécutant des tests sur un objet Dictionary générique.

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); 
     } 
    } 

Je préfère ne pas limiter la méthode avec une contrainte sur T pour assurer les classes ne sont utilisées que si je refondus la méthode comme suit pour contourner le problème avec BeginInvoke mais je ne l'ai pas travaillé avec le directement TreadPool avant et je voudrais m'assurer que je ne manque rien d'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; 
    } 

Répondre

1

Odd, semble être une sorte de bogue dans .NET (C#?) Avec l'argument marshalling le thread de travail.

Si vous implémentez IConvertable sur le struct passé:

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(); 
    } 
} 

Il fonctionne très bien. La conversionType passée ne passe pas .Equal(), IsAssignableFrom(), ou toute autre chose que j'ai essayée à l'exception de la comparaison GUID, qui est probablement liée à la raison pour laquelle elle demande un IConvertable en premier lieu.

EDIT: Une solution simple consiste à utiliser des fermetures pour passer le paramètre:

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); 
} 

Bien sûr, si vous avez besoin des résultats, vous aurez besoin de stocker les IAsyncResults, qui aura probablement la même question que paramètres, dans l'autre sens. Comme alternative, vous pouvez les ajouter à une collection quand ils sont complets, mais le verrouillage devient un peu étrange:

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); 
+0

Merci Simon, je suis content de ne pas avoir négligé quelque chose d'idiot. Je suppose que c'est un bogue dans l'infrastructure provoquant l'échec des vérifications de type, ce qui entraîne la vérification de l'interface IConvertable. Malheureusement, la mise en œuvre d'un type personnalisé n'est pas une option pour ce que j'essaie de faire. Je plus de détails à venir. – Venr