Progettazione per evitare chiamate simultanee a un'implementazione dell'interfaccia

3

L'applicazione che sto sviluppando richiede che alcuni dati siano ottenuti attraverso diversi canali. Poiché si tratta di un processo di rete, ho deciso di utilizzare la programmazione asincrona.

Ho progettato la seguente interfaccia per rappresentare tutti i canali possibili:

public interface IMyDataChannel
{
    Task<MyData> GetMyDataAsync(/* parameters not relevant to the question */);
}

Ho bisogno di ottenere questi dati con parametri diversi, e da canali diversi a volte, quindi idealmente dovrei lanciare tutte le richieste in parallelo per la migliore performance.

Il problema è che uno dei canali è molto instabile, e se lancio diverse richieste in parallelo, o anche sequenzialmente, sovraccarica e restituisce risultati imprevisti. Devo assicurarmi che questo canale non supporti le richieste parallele e aspetta un po 'di tempo dopo ogni richiesta, per evitare di sovraccaricarlo.

Penso che questa logica appartenga all'implementazione del canale, perché i chiamanti non dovrebbero sapere se il canale sottostante è stabile o meno. Invece, dovrebbero recuperare i dati dai canali in modo unificato.

In primo luogo, pensavo che avrei potuto usare un blocco statico nell'implementazione del canale instabile e assicurarmi che il processo desse per qualche tempo prima di rilasciare il blocco. L'implementazione sarebbe come questa:

public class UnstableMyDataChannel : IMyDataChannel
{
    private const int stabilityDelay = 1000;
    private static readonly object stabilityLock = new object();

    public async Task<MyData> GetMyDataAsync(/* parameters not relevant to the question */)
    {
        lock(stabilityLock)
        {
            //Throws a compiler error because you can't "await" inside a "lock"
            MyData data = await PrivateMethodsForRetrievingDataAsync(/* parameters not relevant to the question */);

            //Throws a compiler error because you can't "await" inside a "lock"
            await Task.Delay(stabilityDelay);
            return data;
        }
    }
}

Tuttavia, non è possibile attendere nulla all'interno di un'istruzione di blocco. Anche se tu potessi, questo design non mi convince completamente. Qualcuno può fornire una soluzione migliore (e possibile)?

    
posta Daniel García Rubio 19.02.2018 - 13:29
fonte

1 risposta

5

In primo luogo, creo un decoratore / wrapper che sincronizza le chiamate, invece di metterlo in esecuzione.

E lo implementerei usando la coda invece di usare un lucchetto. TaskQueue da questa altra domanda potrebbe essere utilizzata.

Il mio codice di test rapidamente hackerato:

public interface IMyDataChannel
{
    Task<string> GetMyDataAsync(int parameter);
}

public class MockDataChannel : IMyDataChannel
{
    public async Task<string> GetMyDataAsync(int parameter)
    {
        Console.WriteLine(DateTime.Now.ToLongTimeString() +  " starting " + parameter);

        await Task.Delay(parameter * 300);

        Console.WriteLine(DateTime.Now.ToLongTimeString() + " ending " + parameter);

        return parameter.ToString();
    }
}

public class DataChannelSynchronized : IMyDataChannel
{
    private readonly IMyDataChannel inner;
    private readonly TaskQueue queue = new TaskQueue();

    public DataChannelSynchronized(IMyDataChannel inner)
    {
        this.inner = inner;
    }

    public Task<string> GetMyDataAsync(int parameter)
    {
        return queue.Enqueue(() => Delay(inner.GetMyDataAsync(parameter), 1000));
    }

    public static async Task<T> Delay<T>(Task<T> task, int delay)
    {
        var res = await task;

        await Task.Delay(delay);

        return res;
    }
}

public class Program
{
    public static void Main(string[] args)
    {
        var channel = new DataChannelSynchronized(new MockDataChannel());

        Task.WhenAll(channel.GetMyDataAsync(1),
        channel.GetMyDataAsync(3),
        channel.GetMyDataAsync(4)).Wait();


    }
}
    
risposta data 19.02.2018 - 16:57
fonte

Leggi altre domande sui tag