简体   繁体   English

从ASP.net项目调用静态异步方法

[英]Calling static async methods from ASP.net project

I'm wondering will this scenario be thread safe and are there issues that I'm not currently seeing: 我想知道这个场景是否是线程安全的,是否存在我目前没有看到的问题:

  1. From ASP.net controller I call non-static method from non-static class (this class is in another project, and class is injected into controller). 从ASP.net控制器我从非静态类调用非静态方法(此类在另一个项目中,类被注入到控制器中)。

  2. This method (which is non-static) does some work and calls some other static method passing it userId 这个方法(非静态的)做了一些工作,并调用一些其他静态方法传递userId

  3. Finally static method does some work (for which userId is needed) 最后,静态方法做了一些工作(需要userId)

I believe this approach is thread safe, and that everything will be done properly if two users call this method at the same time (let's say in same nanosecond). 我相信这种方法是线程安全的,并且如果两个用户同时调用此方法,那么一切都将正确完成(让我们说同样的纳秒)。 Am I correct or completely wrong ? 我是正确还是完全错误? If I am wrong what would be correct way of using static methods within ASP.net project ? 如果我错了,在ASP.net项目中使用静态方法的正确方法是什么?

EDIT 编辑

Here is code :) 这是代码:)

This is call from the controller: 这是来自控制器的调用:

await _workoutService.DeleteWorkoutByIdAsync(AzureRedisFeedsConnectionMultiplexer.GetRedisDatabase(),AzureRedisLeaderBoardConnectionMultiplexer.GetRedisDatabase(), workout.Id, userId);

Here how DeleteWorkoutByIdAsync looks like: 这里DeleteWorkoutByIdAsync的样子如下:

public async Task<bool> DeleteWorkoutByIdAsync(IDatabase redisDb,IDatabase redisLeaderBoardDb, Guid id, string userId)
    {

        using (var databaseContext = new DatabaseContext())
        {
            var workout = await databaseContext.Trenings.FindAsync(id);

            if (workout == null)
            {
                return false;
            }

            databaseContext.Trenings.Remove(workout);

            await databaseContext.SaveChangesAsync();

            await RedisFeedService.StaticDeleteFeedItemFromFeedsAsync(redisDb,redisLeaderBoardDb, userId, workout.TreningId.ToString());
        }

        return true;
    }

As you can notice DeleteWorkoutByIdAsync calls static method StaticDeleteFeedItemFromFeedsAsync which looks like this: 您可以注意到DeleteWorkoutByIdAsync调用静态方法StaticDeleteFeedItemFromFeedsAsync,如下所示:

public static async Task StaticDeleteFeedItemFromFeedsAsync(IDatabase redisDb,IDatabase redisLeaderBoardDd, string userId, string workoutId)
 {


        var deleteTasks = new List<Task>();
        var feedAllRedisVals = await redisDb.ListRangeAsync("FeedAllWorkouts:" + userId);
        DeleteItemFromRedisAsync(redisDb, feedAllRedisVals, "FeedAllWorkouts:" + userId, workoutId, ref deleteTasks);


        await Task.WhenAll(deleteTasks);
  }

And here is static method DeleteItemFromRedisAsync which is called in StaticDeleteFeedItemFromFeedsAsync: 这里是静态方法DeleteItemFromRedisAsync,它在StaticDeleteFeedItemFromFeedsAsync中调用:

private static void DeleteItemFromRedisAsync(IDatabase redisDb, RedisValue [] feed, string redisKey, string workoutId, ref List<Task> deleteTasks)
  {
        var itemToRemove = "";

        foreach (var f in feed)
        {

            if (f.ToString().Contains(workoutId))
            {
                itemToRemove = f;
                break;
            }

        }
        if (!string.IsNullOrEmpty(itemToRemove))
        {
            deleteTasks.Add(redisDb.ListRemoveAsync(redisKey, itemToRemove));
        }
  }

Note: this answer was posted before the OP amended their question to add their code, revealing that this is actually a question of whether async/await is thread-safe. 注意:这个答案是在OP修改他们的问题之前发布的,以添加他们的代码,这表明这实际上是async / await是否是线程安全的问题。


Static methods are not a problem in and of themselves. 静态方法本身不是问题。 If a static method is self-contained and manages to do its job using local variables only, then it is perfectly thread safe. 如果静态方法是自包含的并且仅使用局部变量设法完成其工作,那么它是完全线程安全的。

Problems arise if the static method is not self-contained, (delegates to thread-unsafe code,) or if it manipulates static state in a non-thread safe fashion, ie accesses static variables for both read and write outside of a lock() clause. 如果静态方法不是自包含的(委托给线程不安全的代码),或者它以非线程安全的方式操作静态状态,即在lock()外部读取和写入静态变量,则会出现问题条款。

For example, int.parse() and int.tryParse() are static, but perfectly thread safe. 例如, int.parse()int.tryParse()是静态的,但完全是线程安全的。 Imagine the horror if they were not thread-safe. 想象一下如果他们不是线程安全的恐怖。

"Thread safe" isn't a standalone term. “线程安全”不是一个独立的术语。 Thread Safe in the the face of what? 线程安全在面对什么? What kind of concurrent modifications are you expecting here? 你期待什么样的并发修改?

Let's look at a few aspects here: 我们来看几个方面:

  • Your own mutable shared state: You have no shared state whatsoever in this code; 你自己的可变共享状态:你在这段代码中没有任何共享状态; so it's automatically thread safe. 所以它是自动线程安全的。
  • Indirect shared state: DatabaseContext . 间接共享状态: DatabaseContext This looks like an sql database, and those tend to be thread "safe", but what exactly that means depends on the database in question. 这看起来像一个sql数据库,那些往往是线程“安全”,但究竟是什么意思取决于所讨论的数据库。 For example, you're removing a Trenings row, and if some other thread also removes the same row, you're likely to get a (safe) concurrency violation exception. 例如,您正在删除Trenings行,如果某个其他线程也删除了同一行,您可能会遇到(安全)并发冲突异常。 And depending on isolation level, you may get concurrency violation exceptions even for other certain mutations of "Trenings". 并且根据隔离级别,即使对于“Trenings”的其他特定突变,您也可能会遇到并发冲突异常。 At worst that means one failed request, but the database itself won't corrupt. 在最坏的情况下,这意味着一个失败的请求,但数据库本身不会损坏。
  • Redis is essentially single-threaded, so all operations are serialized and in that sense "thread safe" (which might not buy you much). Redis本质上是单线程的,因此所有操作都是序列化的,从这个意义上讲,“线程安全”(这可能不会给你带来太多帮助)。 Your delete code gets a set of keys, then deletes at most one of those. 您的删除代码获取一组密钥,然后最多删除其中一个密钥。 If two or more threads simultaneously attempt to delete the same key, it is possible that one thread will attempt to delete a non-existing key, and that may be unexpected to you (but it won't cause DB corruption). 如果两个或多个线程同时尝试删除相同的密钥,则一个线程可能会尝试删除不存在的密钥,这对您来说可能是意外的(但不会导致数据库损坏)。
  • Implicit consistency between redis+sql: It looks like you're using guids, so the chances of unrelated things clashing are small. redis + sql之间的隐式一致性:看起来你正在使用guid,所以不相关的事情发生冲突的可能性很小。 Your example only contains a delete operation (which is likely no to cause consistency issues), so it's hard to speculate whether under all other circumstances redis and the sql database will stay consistent. 您的示例仅包含删除操作(可能不会导致一致性问题),因此很难推测在所有其他情况下redis和sql数据库是否保持一致。 In general, if your IDs are never reused, you're probably safe - but keeping two databases in sync is a hard problem, and you're quite likely to make a mistake somewhere. 一般来说,如果您的ID永远不会被重用,那么您可能是安全的 - 但保持两个数据库同步是一个难题,而且您很可能在某个地方犯错误。

However, your code seems excessively complicated for what it's doing. 但是,您的代码似乎过于复杂。 I'd recommend you simplify it dramatically if you want to be able to maintain this in the long run. 如果您希望能够长期保持这一点,我建议您大大简化它。

  • Don't use ref parameters unless you really know what you're doing (and it's not necessary here). 除非你真的知道自己在做什么,否则不要使用ref参数(这里没有必要)。
  • Don't mix up strings with other data types, so avoid ToString() where possible. 不要将字符串与其他数据类型混淆,因此请尽可能避免使用ToString() Definitely avoid nasty tricks like Contains to check for key equality. 绝对避免像Contains这样令人讨厌的技巧来检查密钥相等。 You want your code to break when something unexpected happens, because code that "limps along" can be virtually impossible to debug (and you will write bugs). 希望代码在发生意外情况时中断,因为“跛行”的代码几乎无法调试(并且您编写错误)。
  • Don't effectively return an array of tasks if the only thing you can really do is wait for all of them - might as well do that in the callee to simplify the API. 如果您真正能做的唯一事情就是等待所有任务,那么不要有效地返回一系列任务 - 不妨在被调用者中这样做以简化API。
  • Don't use redis. 不要使用redis。 It's probably just a distraction here - you already have another database, so it's very unlikely you need it here, except for performance reasons, and it's extremely premature to go adding whole extra database engines for a hypothetical performance problem. 这可能只是一个分心 - 你已经有了另一个数据库,所以你不太需要它,除了性能原因,并且为假设的性能问题添加额外的数据库引擎还为时过早。 There's a reasonable chance that the extra overhead of requiring extra connections may make your code slower than if you had just one db, especially if you can't save many sql queries. 有一个合理的可能性,即需要额外连接的额外开销可能会使您的代码比只有一个数据库的速度慢 ,特别是如果您无法保存许多SQL查询。

what you are doing here is synchronizing on a list (deleteTasks). 你在这里做的是在列表上同步(deleteTasks)。 If you do this i would recommend 1 of 2 things. 如果你这样做我会推荐2件事中的一件。

1) Either use thread safe collections https://msdn.microsoft.com/en-us/library/dd997305(v=vs.110).aspx 1)使用线程安全集合https://msdn.microsoft.com/en-us/library/dd997305(v=vs.110).aspx

2) Let your DeleteItemFromRedisAsync return a task and await it. 2)让您的DeleteItemFromRedisAsync返回一个任务并等待它。

Although i think in this particular case i don't see any issues as soon as you refactor it and DeleteItemFromRedisAsync can get called multiple times in parallel then you will have issues. 虽然我认为在这种特殊情况下,一旦你重构它就不会发现任何问题,并且DeleteItemFromRedisAsync可以多次并行调用,那么你就会遇到问题。 The reason being is that if multiple threads can modify your list of deleteTasks then you are not longer guaranteed you collect them all ( https://msdn.microsoft.com/en-us/library/dd997373(v=vs.110).aspx if 2 threads do an "Add"/Add-to-the-end in a non-thread safe way at the same time then 1 of them is lost) so you might have missed a task when waiting for all of them to finish. 原因是如果多个线程可以修改你的deleteTasks列表,那么你不再保证你全部收集它们( https://msdn.microsoft.com/en-us/library/dd997373(v=vs.110)。 aspx如果2个线程同时以非线程安全的方式执行“添加”/“添加到结束”,则其中1个丢失)因此您可能在等待所有任务完成时错过了任务。

Also i would avoid mixing paradigms. 我也会避免混合范式。 Either use async/await or keep track of a collection of tasks and let methods add to that list. 使用async / await或跟踪任务集合并让方法添加到该列表。 don't do both. 不要两者都做。 This will help the maintainability of your code in the long run. 从长远来看,这将有助于代码的可维护性。 (note, threads can still return a task, you collect those and then wait for all of them. but then the collecting method is responsible for any threading issues instead of it being hidden in the method that is being called) (注意,线程仍然可以返回一个任务,你收集它们然后等待它们全部。但是收集方法负责任何线程问题,而不是隐藏在被调用的方法中)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM