Как сопоставить настраиваемый объект с соответствующей задачей в следующем сценарии? - PullRequest
1 голос
/ 18 июня 2020

Я разрабатываю консольное приложение, в котором есть сторонний клиент rest (точнее, клиент wordpress rest), в котором есть некоторые методы с объектами return Task. Пример сигнатуры метода может быть такой:

public Task<bool> Delete(int id);

У меня есть список Post, который нужно удалить. Я мог бы сделать что-нибудь просто вроде:

public void DeleteGivenPosts(List<Post> posts) {
  posts.ForEach(async post => await wpRestClient.Delete(post.Id));
}

В этом случае удаление выполняется и забывает. Будет лучше, если я внесу в журнал информацию об удаленном посте. Оператор журнала, например:

logger.Log($"A post with {post.Id} is deleted");

Итак, я решил спроецировать Задачи.

public async Task DeleteGivenPosts(List<Post> posts) {
  var postDeletionTasks = posts.Select(post => wpRestClient.Delete(post.Id));

  foreach (var deletionTask in TaskExtensionUtil.GetTasksInCompletingOrder(postsDeletionTasks)) {
    bool deletionResult = await deletionTask;

    if(deletionResult) {
      //i want to log success of deletion here
    } else {
      //i want to log the failure of deletion here
    }
  }
}

Здесь TaskExtensionUtil.GetTasksInCompletingOrder() - это вспомогательный метод, который возвращает задачи в порядке их выполнения. Код для этого:

public static List<Task<T>> GetTasksInCompletingOrder<T>(IEnumerable<Task<T>> sourceTasks) {
  var sourceTasksArr = sourceTasks.ToArray();
  var taskCompletionSourceArr = new TaskCompletionSource<T>[sourceTasksArr.Length];

  var currentSlot = -1;
  for (int i = 0; i < sourceTasksArr.Length; i++) {
    taskCompletionSourceArr[i] = new TaskCompletionSource<T>();
    sourceTasksArr[i].ContinueWith(prev => {
      int indexToSet = Interlocked.Increment(ref currentSlot);
      taskCompletionSourceArr[indexToSet].SetResult(prev.Result);
    });
  }

  return taskCompletionSourceArr.Select(i => i.Task).ToList();
}

Проблема в том, что deletionResult - это bool. Чтобы регистрировать информацию о том, какой пост был удален, мне нужно получить объект Post, связанный с задачей удаления.

Я думал создать словарь, который сопоставляет задачу удаления с соответствующим Post, выполнив что-то вроде:

posts.Select(post => new { deletionTask = wpRestClient.Delete(post.Id), post})
     .ToDictionary(i => i.deletionTask, i => i.post);

Но это не сработает, потому что в GetTasksInCompletingOrder исходные задачи удаления переведены в TaskCompletionSource задач. Так что я всегда получаю исключение, что ключа нет в словаре. Также я не уверен, как будет вести себя словарь, если в качестве ключей он имеет Task объектов.

На данный момент я не знаю, как добиться ведения журнала. Буду признателен за любую помощь.

Ответы [ 2 ]

3 голосов
/ 18 июня 2020

как насчет записи протокола после удаления?

public async Task DeleteGivenPosts(List<Post> posts)
{
    await Task.WhenAll(
    posts.Select(async post =>
    {
        bool res = await wpRestClient.Delete(post.Id);
        string message = res ? $"Post {post.Id} is deleted" : $"Post {post.Id} survived!";
        logger.Log(message);
    }));
}

Вот небольшая программа LinqPad для проверки работы метода:

async void Main()
{
    List<Post> postList = Enumerable.Range(1, 12).Select(id => new Post {Id = id}).ToList();
    Console.WriteLine("Start Deletion");
    await DeleteGivenPosts(postList);
    Console.WriteLine("Finished Deletion");
}

public static MyRestClient wpRestClient = new MyRestClient();
// Define other methods and classes here
public async Task DeleteGivenPosts(List<Post> posts)
{
    await Task.WhenAll(
    posts.Select(async post =>
    {
        bool res = await wpRestClient.Delete(post.Id);
        string message = res ? $"Post {post.Id} is deleted" : $"Post {post.Id} survived!";
        Console.WriteLine(message);
    }));
}

public static Random rand = new Random(DateTime.Now.Millisecond);

public class MyRestClient
{
    public async Task<bool> Delete(int i)
    {
        return await Task<bool>.Run(() => { Thread.Sleep(400); return rand.Next(1,4) == 1;});
    }
}

public class Post
{
    public int Id { get; set; }
}

Вывод:

Начать удаление
Сообщение 1 удалено
Сообщение 3 сохранилось!
Сообщение 5 сохранилось!
Сообщение 6 сохранилось!
Сообщение 2 сохранилось!
Сообщение 4 сохранилось!
Сообщение 8 сохранилось!
Сообщение 7 сохранилось!
Сообщение 9 сохранилось!
Сообщение 11 сохранилось!
Сообщение 10 удалено
Сообщение 12 удалено
Завершено удаление

1 голос
/ 18 июня 2020

Вы также можете обернуть его своим собственным методом, примерно так:

IEnumerable<Task<DeletionResult>> postDeletionTasks = posts.Select(post => DeletePost(post.Id));

Где ваш метод и класс результата могут выглядеть примерно так:

private async Task<DeletionResult> DeletePost(int postId)
{
    bool result = await wpRestClient.Delete(postId);
    return new DeletionResult(result, postId);
}

и

public class DeletionResult
{
    public DeletionResult(bool result, int postId)
    {
        Result = result;
        PostId = postId;
    }

    public bool Result { get; }
    public int PostId { get; }
}

Таким образом у вас будет список задач, результат которых будет содержать PostId затронутых.

...